axe-api 1.0.0-rc10 → 1.0.0-rc11

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 (31) hide show
  1. package/build/src/Builders/RouterBuilder.js +1 -1
  2. package/build/src/Handlers/Helpers.js +1 -1
  3. package/build/src/Handlers/RequestHandler.js +28 -33
  4. package/build/src/Interfaces.d.ts +10 -14
  5. package/build/src/Middlewares/RateLimit/index.d.ts +17 -0
  6. package/build/src/Middlewares/RateLimit/index.js +21 -1
  7. package/build/src/Model.d.ts +209 -7
  8. package/build/src/Model.js +237 -35
  9. package/build/src/Phases/Delete/ResponsePhase.js +8 -1
  10. package/build/src/Phases/List/ResultPhase.js +8 -1
  11. package/build/src/Phases/Patch/PrepareActionPhase.js +3 -1
  12. package/build/src/Phases/Single/ResultPhase.js +8 -1
  13. package/build/src/Phases/Store/ActionPhase.js +3 -0
  14. package/build/src/Phases/Store/PreparePhase.js +1 -1
  15. package/build/src/Phases/Update/PrepareActionPhase.js +3 -1
  16. package/build/src/Resolvers/ModelResolver.js +2 -2
  17. package/build/src/Resolvers/TransactionResolver.js +3 -15
  18. package/build/src/Server.d.ts +5 -0
  19. package/build/src/Server.js +6 -1
  20. package/build/src/Services/App.d.ts +85 -0
  21. package/build/src/Services/App.js +85 -0
  22. package/build/src/Services/AxeRequest.d.ts +50 -1
  23. package/build/src/Services/AxeRequest.js +50 -4
  24. package/build/src/Services/AxeResponse.d.ts +27 -1
  25. package/build/src/Services/AxeResponse.js +25 -0
  26. package/build/src/Services/IoCService.d.ts +29 -2
  27. package/build/src/Services/IoCService.js +33 -6
  28. package/build/src/Services/LimitService.d.ts +18 -0
  29. package/build/src/Services/LimitService.js +18 -0
  30. package/build/src/Types.d.ts +5 -2
  31. package/package.json +1 -1
@@ -36,7 +36,7 @@ class RouterBuilder {
36
36
  }
37
37
  build() {
38
38
  return __awaiter(this, void 0, void 0, function* () {
39
- const app = yield Services_1.IoCService.useByType("App");
39
+ const app = yield Services_1.IoCService.use("App");
40
40
  const generalHooks = yield new Resolvers_1.GeneralHookResolver(this.version).resolve();
41
41
  if (generalHooks.onBeforeInit) {
42
42
  generalHooks.onBeforeInit(app);
@@ -50,7 +50,7 @@ const callHooks = (model, type, params) => __awaiter(void 0, void 0, void 0, fun
50
50
  // we don't await for the events. If the developer uses the transaction and
51
51
  // try to commit something, it would be lost cause the transaction could be
52
52
  // already completed.
53
- const database = (yield Services_1.IoCService.use("Database"));
53
+ const database = yield Services_1.IoCService.use("Database");
54
54
  params.database = database;
55
55
  // Calling the events
56
56
  model.events[type](params);
@@ -15,24 +15,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const Services_1 = require("../Services");
16
16
  const URLService_1 = __importDefault(require("../Services/URLService"));
17
17
  const ConverterService_1 = require("../Services/ConverterService");
18
+ const Enums_1 = require("../Enums");
18
19
  const api = Services_1.APIService.getInstance();
19
20
  const return404 = (response) => {
20
21
  response.statusCode = 404;
21
22
  response.write(JSON.stringify({ error: "Resource not found" }));
22
23
  response.end();
23
24
  };
24
- const callPhases = (phases, context, match, trx, axeResponse) => __awaiter(void 0, void 0, void 0, function* () {
25
- for (const phase of phases) {
25
+ exports.default = (request, response) => __awaiter(void 0, void 0, void 0, function* () {
26
+ Services_1.LogService.debug(`${request.method} ${request.url}`);
27
+ const { axeRequest, axeResponse } = (0, ConverterService_1.toAxeRequestResponsePair)(request, response);
28
+ const match = URLService_1.default.match(axeRequest);
29
+ if (!match) {
30
+ Services_1.LogService.warn(`The URL is not matched! ${request.method} ${request.url}`);
31
+ return return404(response);
32
+ }
33
+ // We should set the params
34
+ axeRequest.params = match.params;
35
+ const database = yield Services_1.IoCService.use("Database");
36
+ // Prepare the database by the transaction option
37
+ let trx = null;
38
+ if (match.hasTransaction) {
39
+ Services_1.LogService.warn("\tDB transaction created");
40
+ trx = yield database.transaction();
41
+ }
42
+ const context = Object.assign(Object.assign({}, match.data), { params: match.params, api, req: axeRequest, res: axeResponse, isTransactionOpen: match.hasTransaction, database: match.hasTransaction && trx ? trx : database });
43
+ response.setHeader("Content-Type", "application/json");
44
+ response.setHeader("x-powered-by", "Axe API");
45
+ for (const phase of match.phases) {
26
46
  // If there is an non-async phase, it should be an Event function
27
47
  if (phase.isAsync === false) {
28
- Services_1.LogService.debug(`\t${phase.name}()`);
29
- yield phase.callback(context);
48
+ phase.callback(context);
49
+ Services_1.LogService.debug(`\t${phase.name}()✓`);
30
50
  continue;
31
51
  }
32
52
  // Middleware and hook calls
33
53
  try {
34
- Services_1.LogService.debug(`\t${phase.name}()`);
35
54
  yield phase.callback(context);
55
+ Services_1.LogService.debug(`\t✓ ${phase.name}()`);
36
56
  }
37
57
  catch (error) {
38
58
  Services_1.LogService.error(`\t${error.message} ${phase.callback}`);
@@ -48,7 +68,9 @@ const callPhases = (phases, context, match, trx, axeResponse) => __awaiter(void
48
68
  .json({ error: apiError.message });
49
69
  }
50
70
  // TODO: We need an error handler.
51
- axeResponse.status(500).json({ error: error.toString() });
71
+ axeResponse
72
+ .status(Enums_1.StatusCodes.INTERNAL_SERVER_ERROR)
73
+ .json({ error: error.toString() });
52
74
  break;
53
75
  }
54
76
  // If the response is not created, we should go to the next phase
@@ -65,35 +87,8 @@ const callPhases = (phases, context, match, trx, axeResponse) => __awaiter(void
65
87
  Services_1.LogService.debug(`\tResponse ${context.res.statusCode()}`);
66
88
  break;
67
89
  }
68
- // If there is a valid transaction, we should commit it
69
- if (match.hasTransaction && trx) {
70
- Services_1.LogService.warn("\tDB transaction commit");
71
- trx.commit();
72
- }
73
90
  Services_1.LogService.debug(`\tResponse ${context.res.statusCode()}`);
74
91
  // We should brake the for-loop
75
92
  break;
76
93
  }
77
94
  });
78
- exports.default = (request, response) => __awaiter(void 0, void 0, void 0, function* () {
79
- Services_1.LogService.debug(`${request.method} ${request.url}`);
80
- const { axeRequest, axeResponse } = (0, ConverterService_1.toAxeRequestResponsePair)(request, response);
81
- const match = URLService_1.default.match(axeRequest);
82
- if (!match) {
83
- Services_1.LogService.warn(`The URL is not matched! ${request.method} ${request.url}`);
84
- return return404(response);
85
- }
86
- // We should set the params
87
- axeRequest.params = match.params;
88
- const database = (yield Services_1.IoCService.use("Database"));
89
- // Prepare the database by the transaction option
90
- let trx = null;
91
- if (match.hasTransaction) {
92
- Services_1.LogService.warn("\tDB transaction created");
93
- trx = yield database.transaction();
94
- }
95
- const context = Object.assign(Object.assign({}, match.data), { params: match.params, api, req: axeRequest, res: axeResponse, database: match.hasTransaction && trx ? trx : database });
96
- response.setHeader("Content-Type", "application/json");
97
- response.setHeader("x-powered-by", "Axe API");
98
- yield callPhases(match.phases, context, match, trx, axeResponse);
99
- });
@@ -5,7 +5,7 @@ import { Options as FormOptions } from "formidable";
5
5
  import { Column } from "knex-schema-inspector/lib/types/column";
6
6
  import { HandlerTypes, HttpMethods, HookFunctionTypes, Extensions, Relationships, SortTypes, ConditionTypes, DependencyTypes, QueryFeature, QueryFeatureType } from "./Enums";
7
7
  import Model from "./Model";
8
- import { AdaptorTypes, HandlerFunction, HookFunctions, MiddlewareFunction, StepTypes, PhaseFunction, SerializationFunction } from "./Types";
8
+ import { AdaptorTypes, HandlerFunction, HookFunctions, MiddlewareFunction, PhaseFunction, SerializationFunction } from "./Types";
9
9
  import { ModelListService, QueryService } from "./Services";
10
10
  import AxeRequest from "./Services/AxeRequest";
11
11
  import AxeResponse from "./Services/AxeResponse";
@@ -18,7 +18,7 @@ export interface IColumn extends Column {
18
18
  export interface IConfig {
19
19
  }
20
20
  export interface IHandlerBasedTransactionConfig {
21
- handler: HandlerTypes | HandlerTypes[];
21
+ handlers: HandlerTypes[];
22
22
  transaction: boolean;
23
23
  }
24
24
  interface IHandlerBasedSerializer {
@@ -72,7 +72,7 @@ export interface AxeConfig extends IConfig {
72
72
  env: string;
73
73
  port: number;
74
74
  prefix: string;
75
- database: IDatabaseConfig;
75
+ database: Knex.Config;
76
76
  pino: LoggerOptions;
77
77
  rateLimit: IRateLimitConfig;
78
78
  }
@@ -86,7 +86,6 @@ export interface IAcceptedLanguage {
86
86
  language: ILanguage;
87
87
  quality: number;
88
88
  }
89
- export type IDatabaseConfig = Knex.Config;
90
89
  export interface IVersionFolder {
91
90
  root: string;
92
91
  config: string;
@@ -113,18 +112,14 @@ export interface IGeneralHooks {
113
112
  onBeforeInit: (app: App) => void | null;
114
113
  onAfterInit: (app: App) => void | null;
115
114
  }
116
- export interface IHandlerBaseMiddleware {
115
+ export interface IHandlerBaseConfig<T> {
117
116
  handler: HandlerTypes[];
118
- middleware: StepTypes;
117
+ middleware: T;
119
118
  }
120
- export interface IMethodBaseConfig {
121
- [HttpMethods.POST]?: string[];
122
- [HttpMethods.PUT]?: string[];
123
- [HttpMethods.PATCH]?: string[];
124
- }
125
- export interface IMethodBaseValidations {
126
- [HttpMethods.POST]?: Record<string, string>;
127
- [HttpMethods.PUT]?: Record<string, string>;
119
+ export interface IMethodBaseConfig<T> {
120
+ [HttpMethods.POST]?: T;
121
+ [HttpMethods.PUT]?: T;
122
+ [HttpMethods.PATCH]?: T;
128
123
  }
129
124
  export interface IModelService {
130
125
  name: string;
@@ -162,6 +157,7 @@ export interface IRequestPack extends IRouteData {
162
157
  req: AxeRequest;
163
158
  res: AxeResponse;
164
159
  database: Knex | Knex.Transaction;
160
+ isTransactionOpen: boolean;
165
161
  queryParser?: QueryService;
166
162
  conditions?: IQuery;
167
163
  query?: Knex.QueryBuilder;
@@ -1,6 +1,23 @@
1
1
  import { IncomingMessage, ServerResponse } from "http";
2
2
  import { AxeConfig, IRateLimitOptions, IRequestPack } from "../../Interfaces";
3
3
  export declare const setupRateLimitAdaptors: (config: AxeConfig) => void;
4
+ /**
5
+ * Add a rate limit with the `IRateLimitOptions`
6
+ *
7
+ * @param options
8
+ * @returns
9
+ * @example
10
+ * class User extends Model {
11
+ * get middlewares() {
12
+ * return [
13
+ * {
14
+ * handler: [HandlerTypes.INSERT],
15
+ * middleware: rateLimit({ maxRequests: 200, windowInSeconds: 5 }),
16
+ * },
17
+ * ];
18
+ * }
19
+ * }
20
+ */
4
21
  export declare const rateLimit: (options?: IRateLimitOptions) => (context: IRequestPack) => Promise<void>;
5
22
  declare const _default: (req: IncomingMessage, res: ServerResponse, next: any) => Promise<any>;
6
23
  export default _default;
@@ -16,6 +16,7 @@ exports.rateLimit = exports.setupRateLimitAdaptors = void 0;
16
16
  const AdaptorFactory_1 = __importDefault(require("./AdaptorFactory"));
17
17
  const Services_1 = require("../../Services");
18
18
  const nanoid_1 = require("nanoid");
19
+ const Enums_1 = require("../../Enums");
19
20
  let adaptor;
20
21
  const checkRateLimit = function (clientKey, options) {
21
22
  return __awaiter(this, void 0, void 0, function* () {
@@ -69,6 +70,23 @@ const setupRateLimitAdaptors = (config) => {
69
70
  adaptor = (0, AdaptorFactory_1.default)(((_a = config.rateLimit) === null || _a === void 0 ? void 0 : _a.adaptor.type) || "memory", (_b = config.rateLimit) === null || _b === void 0 ? void 0 : _b.adaptor.redis, "");
70
71
  };
71
72
  exports.setupRateLimitAdaptors = setupRateLimitAdaptors;
73
+ /**
74
+ * Add a rate limit with the `IRateLimitOptions`
75
+ *
76
+ * @param options
77
+ * @returns
78
+ * @example
79
+ * class User extends Model {
80
+ * get middlewares() {
81
+ * return [
82
+ * {
83
+ * handler: [HandlerTypes.INSERT],
84
+ * middleware: rateLimit({ maxRequests: 200, windowInSeconds: 5 }),
85
+ * },
86
+ * ];
87
+ * }
88
+ * }
89
+ */
72
90
  const rateLimit = (options) => {
73
91
  // For each model middleware, we should use a different ID for the cache key
74
92
  const id = (0, nanoid_1.nanoid)();
@@ -88,7 +106,9 @@ const rateLimit = (options) => {
88
106
  // Sending an error message if there is an error
89
107
  if (isAllowed.success === false) {
90
108
  Services_1.LogService.warn(`Rate limit exceeded: ${context.req.url}`);
91
- context.res.status(429).json({ error: "Rate limit exceeded." });
109
+ context.res
110
+ .status(Enums_1.StatusCodes.TOO_MANY_REQUESTS)
111
+ .json({ error: "Rate limit exceeded." });
92
112
  }
93
113
  });
94
114
  };
@@ -1,26 +1,228 @@
1
- import { IRelation, IMethodBaseConfig, IMethodBaseValidations, IHandlerBasedTransactionConfig, IQueryLimitConfig } from "./Interfaces";
1
+ import { IRelation, IMethodBaseConfig, IQueryLimitConfig, IHandlerBasedTransactionConfig } from "./Interfaces";
2
2
  import { HandlerTypes, HttpMethods } from "./Enums";
3
3
  import { FieldList, ModelMiddlewareDefinition, StepTypes, ModelValidation } from "./Types";
4
4
  declare class Model {
5
+ /**
6
+ * The primary key of the model. By default, it is `id`. But you can choose
7
+ * another name like `uuid`.
8
+ *
9
+ * @example
10
+ * get primaryKey() {
11
+ * return "id"
12
+ * }
13
+ * @type {string}
14
+ * @tutorial https://axe-api.com/reference/model-primary-key.html
15
+ */
5
16
  get primaryKey(): string;
17
+ /**
18
+ * The database table name of the model. By default, Axe API uses the plural
19
+ * version of the model(`User.ts` => `users`) name. You can specify a custom
20
+ * name like `my_users`.
21
+ *
22
+ * @example
23
+ * get table() {
24
+ * return "my_users_table"
25
+ * }
26
+ * @type {string}
27
+ * @tutorial https://axe-api.com/reference/model-table.html
28
+ */
6
29
  get table(): string;
7
- get fillable(): FieldList | IMethodBaseConfig;
8
- get validations(): IMethodBaseValidations | ModelValidation;
30
+ /**
31
+ * By this method, you can define which fields can be filled by the HTTP client.
32
+ * If you do not define, the HTTP client can not fill any field.
33
+ *
34
+ * @example
35
+ * get fillable() {
36
+ * return ["name", "email", "password"]
37
+ * }
38
+ * @type {(FieldList | IMethodBaseConfig)}
39
+ * @tutorial https://axe-api.com/reference/model-fillable.html
40
+ */
41
+ get fillable(): FieldList | IMethodBaseConfig<FieldList>;
42
+ /**
43
+ * You can define the validation rules of the model. Method-based validations
44
+ * are also acceptable.
45
+ *
46
+ * @example
47
+ * get validations() {
48
+ * return {
49
+ * "name": "required|min:1|max:100",
50
+ * "email": "required|email",
51
+ * }
52
+ * }
53
+ * @type {(ModelValidation | IMethodBaseConfig<ModelValidation>)}
54
+ * @tutorial https://axe-api.com/reference/model-validations.html
55
+ */
56
+ get validations(): ModelValidation | IMethodBaseConfig<ModelValidation>;
57
+ /**
58
+ * You can define acceptable handlers here.
59
+ *
60
+ * The default value is `DEFAULT_HANDLERS`
61
+ *
62
+ * @example
63
+ * get handlers() {
64
+ * return [HandlerTypes.PAGINATE]
65
+ * }
66
+ * @type {HandlerTypes[]}
67
+ * @tutorial https://axe-api.com/reference/model-handlers.html
68
+ */
9
69
  get handlers(): HandlerTypes[];
70
+ /**
71
+ * You can define a special handler for the model here. `MiddlewareFunction`,
72
+ * `HandlerFunction`, and `PhaseFunction` are acceptable middlewares.
73
+ *
74
+ * Also, you can define handler-based middlewares.
75
+ *
76
+ * @example
77
+ * get middlewares() {
78
+ * return [
79
+ * {
80
+ * handler: [HandlerTypes.DELETE],
81
+ * middleware: isAdminMiddleware,
82
+ * }
83
+ * ]
84
+ * }
85
+ * @type {ModelMiddlewareDefinition}
86
+ * @tutorial https://axe-api.com/reference/model-middlewares.html
87
+ */
10
88
  get middlewares(): ModelMiddlewareDefinition;
89
+ /**
90
+ * You can define which fields will be hiding in HTTP responses. The selected
91
+ * fields will not be listed in any response.
92
+ *
93
+ * You should mark as hidden sensitive data fields such as `password`, `token`, etc.
94
+ *
95
+ * @example
96
+ * get hiddens() {
97
+ * return ["password_salt", "password_hash", "github_token"]
98
+ * }
99
+ * @type {FieldList}
100
+ * @tutorial https://axe-api.com/reference/model-hiddens.html
101
+ */
11
102
  get hiddens(): FieldList;
103
+ /**
104
+ * The `created_at` column name is in the database table. The default value is
105
+ * `created_at`. You can specify with a custom field name.
106
+ *
107
+ * It should be null if the table doesn't have a `created_at` column.
108
+ *
109
+ * @example
110
+ * get createdAtColumn() {
111
+ * return "created_at"
112
+ * }
113
+ * @type {(string | null)}
114
+ * @tutorial https://axe-api.com/reference/model-created-at-column.html
115
+ */
12
116
  get createdAtColumn(): string | null;
117
+ /**
118
+ * The `updated_at` column name is in the database table. The default value is
119
+ * `updated_at`. You can specify with a custom field name.
120
+ *
121
+ * It should be null if the table doesn't have a `updated_at` column.
122
+ *
123
+ * @example
124
+ * get updatedAtColumn() {
125
+ * return "updated_at"
126
+ * }
127
+ * @type {(string | null)}
128
+ * @tutorial https://axe-api.com/reference/model-updated-at-column.html
129
+ */
13
130
  get updatedAtColumn(): string | null;
131
+ /**
132
+ * The `deleted_at` column name is in the database table. The default value is
133
+ * `null`. You can specify with a custom field name.
134
+ *
135
+ * If you provide a name, that means your model supports the soft delete feature.
136
+ *
137
+ * @example
138
+ * get deletedAtColumn() {
139
+ * return "deleted_at"
140
+ * }
141
+ * @type {(string | null)}
142
+ * @tutorial https://axe-api.com/reference/model-deleted-at-column.html
143
+ */
14
144
  get deletedAtColumn(): string | null;
15
- get transaction(): boolean | IHandlerBasedTransactionConfig | IHandlerBasedTransactionConfig[] | null;
145
+ /**
146
+ * The transaction configuration on the model. The database transaction can
147
+ * be started for all endpoints on the model as well as handler-based.
148
+ *
149
+ * Creating, rollbacking, and committing a transaction is managed by Axe API.
150
+ *
151
+ * @example
152
+ * get transaction() {
153
+ * return [
154
+ * {
155
+ * handlers: [HandlerTypes.INSERT],
156
+ * transaction: true
157
+ * }
158
+ * ]
159
+ * }
160
+ * @type {(boolean | IHandlerBasedTransactionConfig[])}
161
+ * @tutorial https://axe-api.com/learn/database-transactions.html#model-based-transactions
162
+ */
163
+ get transaction(): boolean | IHandlerBasedTransactionConfig[];
164
+ /**
165
+ * You can completely ignore the model. Axe API doesn't create the routes
166
+ * automatically.
167
+ *
168
+ * @example
169
+ * get ignore() {
170
+ * return true
171
+ * }
172
+ * @type {boolean}
173
+ * @tutorial https://axe-api.com/reference/model-ignore.html
174
+ */
16
175
  get ignore(): boolean;
176
+ /**
177
+ * You can limit query features such as `select.*` or `LIKE`, etc.
178
+ *
179
+ * @example
180
+ * get limits() {
181
+ * return [
182
+ * allow(QueryFeature.WhereLike),
183
+ * deny(QueryFeature.FieldsAll)
184
+ * ];
185
+ * }
186
+ * @type {Array<IQueryLimitConfig[]>}
187
+ * @tutorial https://axe-api.com/reference/model-limits.html
188
+ */
17
189
  get limits(): Array<IQueryLimitConfig[]>;
18
- getFillableFields(methodType: HttpMethods): string[];
19
- getValidationRules(methodType: HttpMethods): ModelValidation | null;
20
- getMiddlewares(handlerType: HandlerTypes): StepTypes[];
190
+ /**
191
+ * Model relationship definition. Axe API creates `hasMany` routes automatically.
192
+ *
193
+ * @example
194
+ * get relationName() {
195
+ * return this.hasMany("Post", "id", "user_id")
196
+ * }
197
+ * @type {Array<IQueryLimitConfig[]>}
198
+ * @tutorial https://axe-api.com/learn/routing.html#model-relations
199
+ */
21
200
  hasMany(relatedModel: string, primaryKey?: string, foreignKey?: string): IRelation;
201
+ /**
202
+ * Model relationship definition.
203
+ *
204
+ * @example
205
+ * get relationName() {
206
+ * return this.hasOne("User", "id", "user_id")
207
+ * }
208
+ * @type {Array<IQueryLimitConfig[]>}
209
+ * @tutorial https://axe-api.com/learn/routing.html#model-relations
210
+ */
22
211
  hasOne(relatedModel: string, primaryKey?: string, foreignKey?: string): IRelation;
212
+ /**
213
+ * Model relationship definition.
214
+ *
215
+ * @example
216
+ * get relationName() {
217
+ * return this.belongsTo("User", "user_id", "id")
218
+ * }
219
+ * @type {Array<IQueryLimitConfig[]>}
220
+ * @tutorial https://axe-api.com/learn/routing.html#model-relations
221
+ */
23
222
  belongsTo(relatedModel: string, primaryKey: string, foreignKey: string): IRelation;
223
+ getFillableFields(methodType: HttpMethods): string[];
224
+ getValidationRules(methodType: HttpMethods): ModelValidation | null;
225
+ getMiddlewares(handlerType: HandlerTypes): StepTypes[];
24
226
  private hasStringValue;
25
227
  }
26
228
  export default Model;