mythix 2.12.1 → 3.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/README.md CHANGED
@@ -1,17 +1,15 @@
1
1
  # mythix
2
2
 
3
- Mythix is a NodeJS web-app framework. It is configured to have sane defaults so that you need not worry about configuration. However, it was designed such that any part of the default application can be overloaded to provide custom functionality for any components of the framework.
4
-
5
- ## Heads UP!
3
+ ![Mythix](docs/mythix-logo-colored.png)
6
4
 
7
- Though mythix is currently quite useful, it is still in "beta" phase, and is missing some useful functionality. Don't let this deter you, just be aware that it is still heavily under development. Help via PRs is always welcome!
5
+ Mythix is a NodeJS web-app framework. It is configured to have sane defaults so that you need not worry about configuration. However, it was designed such that any part of the default application can be overloaded to provide custom functionality for any components of the framework.
8
6
 
9
7
  ## Install
10
8
 
11
9
  To create a new empty mythix project:
12
10
 
13
11
  ```bash
14
- $ npx mythix-cli init my_project_name
12
+ $ npx mythix-cli create my_project_name
15
13
  ```
16
14
 
17
15
  Or to install directly as a dependency:
@@ -33,16 +31,16 @@ projectRoot/ ->
33
31
  |---- models/ (all model definitions)
34
32
  |---- seeders/ (database seeders)
35
33
  |---- tasks/ (reoccuring/cron-type tasks)
34
+ |---- routes/ (route definitions for your web-app)
36
35
  |---- application.js (application class definition)
37
36
  |---- index.js (entry point for your application)
38
- |---- routes.js (route definitions for your web-app)
39
- | .mythix-config.js (custom mythix RC)
40
- | package.json
37
+ |-- .mythix-config.js (custom mythix RC)
38
+ |-- package.json
41
39
  ```
42
40
 
43
41
  ## Creating the application
44
42
 
45
- To create your own `mythix` application, you simply need to inherit from `Mythix.Application`:
43
+ To create your own `mythix` application, you simply need to inherit from the `Mythix.Application` class:
46
44
 
47
45
  Example:
48
46
 
@@ -92,7 +90,7 @@ Or, you can simply invoke your own entry point:
92
90
  $ node app/index.js
93
91
  ```
94
92
 
95
- Your `index.js` simply needs to create an instance of your `Application` and call `appInstance.start()` to start your server.
93
+ Your `index.js` simply needs to create an instance of your `Application` class and call `await appInstance.start()` on it to start your server.
96
94
 
97
95
  ## Application configuration
98
96
 
@@ -138,132 +136,106 @@ Now, all you need to do is add your new controller to the routes:
138
136
  Simply modify `./app/routes.js` to have the following content:
139
137
 
140
138
  ```
141
- module.exports = function getRoutes() {
142
- return {
143
- 'api': {
144
- 'v1': {
145
- 'greet': [
146
- {
147
- 'methods': [ 'GET' ],
148
- 'controller': 'HelloWorld.greet',
149
- },
150
- ],
151
- },
152
- },
153
- };
154
- };
155
- ```
156
-
157
- That is it! Now you can goto `http://localhost:8001/api/v1/greet` and you will see `Hello world!` in your browser.
158
-
159
- ## Defining routes
160
-
161
- Routes are defined using a simple object structure. The method `getRoutes` is called on your application to get these routes. Routes can contain "captures" to capture path information that will be passed onto your controller. The syntax for these "captures" is simple: `<param:type>`, for example: `<id:integer>`. Params can also specify default values: `<enabled:boolean=true>`. Or, params can be optional: `<id?:integer>`.
162
-
163
- *Note: If an optional param immediately follows a forward slash, i.e. `/somepath/<id?>` then the preceeding forward slash is also optional. So the previous example would match on `/somepath` and `/somepath/234`.*
164
-
165
- Params types are:
166
-
167
- * `string`
168
- * `integer`
169
- * `number`
170
- * `boolean`
171
- * `bigint`
172
-
173
- If no type is specified, then `mythix` will "guess" the type as best as it is able.
174
-
175
- Example 1:
176
-
177
- ```javascript
178
- module.exports = function getRoutes() {
179
- return {
180
- 'api': {
181
- 'v1': {
182
- // CRUD routes for "users"
183
- 'users': [
184
- // If there is a param of "/id", then capture
185
- // it and pass it to the controller
186
- '/<id:integer>': [
187
- {
188
- 'methods': [ 'GET' ],
189
- 'accept': [ 'application/json' ],
190
- 'controller': 'User.show',
191
- },
139
+ module.exports = function getRoutes({ path }) {
140
+ path('api', ({ path }) => {
141
+ path('v1', ({ endpoint }) => {
142
+ endpoint('greet', {
143
+ name: 'greet', // Name of the API method in Javascript
144
+ methods: [ 'GET', 'POST' ],
145
+ controller: 'UserController.showCurrentUser', // The controller to use
146
+ help: {
147
+ 'description': 'Greet the user (example).',
148
+ 'data': [
192
149
  {
193
- 'methods': [ 'PUT' ],
194
- 'accept': [ 'application/json' ],
195
- 'controller': 'User.update',
150
+ 'property': 'name',
151
+ 'type': 'string',
152
+ 'description': 'Name to use to greet the user',
153
+ 'required': true,
196
154
  },
155
+ ],
156
+ 'params': [
197
157
  {
198
- 'methods': [ 'DELETE' ],
199
- 'accept': [ 'application/json' ],
200
- 'controller': 'User.delete',
158
+ 'property': 'userID',
159
+ 'type': 'string',
160
+ 'description': 'ID of user to greet',
161
+ 'required': true,
201
162
  },
202
163
  ],
203
- {
204
- 'methods': [ 'GET' ],
205
- 'accept': [ 'application/json' ],
206
- 'controller': 'User.index',
207
- },
208
- {
209
- 'methods': [ 'POST' ],
210
- 'accept': [ 'application/json' ],
211
- 'controller': 'User.create',
212
- },
213
- ],
214
- },
215
- },
216
- };
164
+ 'example': 'await API.greet({ data: { name: \'My Name\' }, params: { userID: \'some-user-id\' } });',
165
+ 'notes': [
166
+ 'This is just an example help section',
167
+ 'We don\'t really need a userID for params...',
168
+ 'This help can be shown simply by accessing `API.greet.help` from the development console',
169
+ ],
170
+ },
171
+ });
172
+ });
173
+ });
217
174
  };
218
175
  ```
219
176
 
220
- Example 2:
177
+ That is it! Now you can goto `http://localhost:8001/api/v1/greet` and you will see `Hello world!` in your browser.
178
+
179
+ ## Defining routes
180
+
181
+ Routes are defined using methods. The method `getRoutes` is called on your application to build routes. When called, this method will be provided a `context` as a single argument, which contains `path`, `endpoint`, and `capture` methods used to build routes.
221
182
 
222
- You can also be more direct:
183
+ Example:
223
184
 
224
185
  ```javascript
225
- module.exports = function getRoutes() {
226
- return {
227
- // The '?' makes this param optional
228
- // either /api/v1/users
229
- // or /api/v1/users/234
230
- // will work
231
- 'api/v1/users/<id?:integer>': [
232
- {
233
- 'methods': [ 'GET' ],
234
- 'accept': [ 'application/json' ],
235
- 'requireParams': [ 'id' ],
236
- 'controller': 'User.show',
237
- },
238
- {
239
- 'methods': [ 'PUT' ],
240
- 'accept': [ 'application/json' ],
241
- 'controller': 'User.update',
242
- },
243
- {
244
- 'methods': [ 'DELETE' ],
245
- 'accept': [ 'application/json' ],
246
- 'controller': 'User.delete',
247
- },
248
- {
249
- 'methods': [ 'GET' ],
250
- 'accept': [ 'application/json' ],
251
- 'controller': 'User.index',
252
- },
253
- {
254
- 'methods': [ 'POST' ],
255
- 'accept': [ 'application/json' ],
256
- 'controller': 'User.create',
257
- },
258
- },
259
- },
260
- };
186
+ module.exports = function({ path }) {
187
+ path('api', ({ path }) => {
188
+ path('v1', ({ endpoint, capture }) => {
189
+ path('user', ({ endpoint, capture }) => {
190
+ // Create a capture named "userID"
191
+ let userID = capture('userID', { type: 'integer' });
192
+
193
+ // GET /api/v1/user/{userID}
194
+ // By default the `methods` property for each endpoint is `[ 'GET' ]`
195
+ endpoint(userID, {
196
+ name: 'getUser',
197
+ controller: 'UserController.show',
198
+ });
199
+
200
+ // PATCH /api/v1/user/{userID}
201
+ endpoint(userID, {
202
+ name: 'updateUser',
203
+ methods: [ 'PATCH' ],
204
+ controller: 'UserController.update',
205
+ });
206
+
207
+ // PATCH /api/v1/user/{userID}
208
+ endpoint(userID, {
209
+ name: 'updateUser',
210
+ methods: [ 'PATCH' ],
211
+ controller: 'UserController.update',
212
+ });
213
+
214
+ // /api/v1/user/{userID}/
215
+ path(userID, ({ endpoint }) => {
216
+ // PUT /api/v1/user/{userID}/tags
217
+ endpoint('tags', {
218
+ name: 'addUserTags',
219
+ methods: [ 'PUT' ],
220
+ controller: 'UserController.addTags',
221
+ });
222
+
223
+ // DELETE /api/v1/user/{userID}/tags
224
+ endpoint('tags', {
225
+ name: 'removeUserTags',
226
+ methods: [ 'DELETE' ],
227
+ controller: 'UserController.removeTags',
228
+ });
229
+ });
230
+ });
231
+ });
232
+ });
261
233
  };
262
234
  ```
263
235
 
264
236
  ## Defining models
265
237
 
266
- `mythix` uses [sequelize](https://www.npmjs.com/package/sequelize) under the hood for its ORM. Defining models in `mythix` is nearly identical to how they are defined in [sequelize](https://www.npmjs.com/package/sequelize), with a few bits of syntatic sugar mixed in.
238
+ `mythix` uses [mythix-orm](https://www.npmjs.com/package/mythix-orm) under the hood for its ORM. See the [documentation](https://github.com/th317erd/mythix-orm/wiki) for `mythix-orm` for details.
267
239
 
268
240
  First, you need to start by defining your models with the `Mythix.defineModel` method. This method needs the name of your model as its first argument, a `definer` method that will return your model class, and optionally a parent model to inherit from.
269
241
 
@@ -276,34 +248,48 @@ Example:
276
248
  ```javascript
277
249
  const { defineModel } = require('mythix');
278
250
 
279
- module.exports = defineModel('Product', ({ Parent, Type, Relation }) => {
251
+ module.exports = defineModel('Product', ({ Parent, Types }) => {
280
252
  return class Product extends Parent {
281
- // Define the model fields, using Sequelize syntax (mostly)
253
+ // Define the model fields, using mythix-orm
282
254
  static fields = {
255
+ ...(Parent.fields || {}),
283
256
  id: {
284
- type: Type.UUID,
285
- defaultValue: Type.UUIDV4,
257
+ type: Types.UUIDV4,
258
+ defaultValue: Types.UUIDV4.Default.UUIDV4(),
286
259
  primaryKey: true,
287
260
  },
288
261
  name: {
289
- type: Type.STRING(32),
262
+ type: Types.STRING(32),
290
263
  allowNull: false,
291
- // "index" is not sequelize syntax... this is mythix sugar
292
- // for a simple way to define that we want to index this column
293
264
  index: true,
294
265
  },
295
266
  price: {
296
- type: Type.FLOAT,
267
+ type: Types.NUMERIC(),
297
268
  allowNull: false,
298
269
  index: true,
299
270
  },
271
+ // Relationship to "Orders" table
272
+ orders: {
273
+ type: Types.Models('Order', async ({ self }, { Order, LineItem }, userQuery) => {
274
+ // Pull distinct orders
275
+ return Order
276
+ .$.DISTINCT
277
+ // Joining the Order table with the LineItem table
278
+ // where LineItem.orderID equals Order.id
279
+ .id
280
+ .EQ(LineItem.where.orderID)
281
+ .AND
282
+ // And the line item contains this
283
+ // product id
284
+ .LineItem.productID
285
+ .EQ(self.id)
286
+ // Now tack on any extra query the
287
+ // user specified
288
+ .MERGE(userQuery);
289
+ }),
290
+ },
300
291
  };
301
292
 
302
- // Define model relations
303
- static relations = [
304
- Relation.belongsTo('Order', { allowNull: false, onDelete: 'RESTRICT', name: 'order' }),
305
- ];
306
-
307
293
  // Optionally define model methods
308
294
  // ...
309
295
  };
@@ -368,15 +354,13 @@ $ mythix-cli deploy --target ssh://host/path/
368
354
 
369
355
  ## Migrations
370
356
 
371
- Unfortunately the migration commands in `mythix` are currently barely functional. The best you can do is create an "initial" migration from your model definitions. This means that (aside from creating custom migrations), you are left with nothing to do but drop your database, and re-create it every time model schemas change. I hope to have these scripts smarter and fixed in the near future. For now, you can create an "initial" migration by running the following command:
357
+ Unfortunately the migration commands in `mythix` are currently being developed. Right now it is possible to add models and fields... soon I hope to have complete migration functionality built-in. For now, you can run the command below to add models. A similar `add fields` command can be ran to add specific fields to a model:
372
358
 
373
359
  ```bash
374
- $ mythix-cli makemigrations --name initial
360
+ $ mythix-cli generate migration --name new-models add models Product Order LineItem
375
361
  ```
376
362
 
377
- This will create a migration in the `./app/migrations` folder to setup the initial schema of your database.
378
-
379
- If model schemas change, then you will need to drop and re-create your database, delete all the files in `./app/migrations`, and run `mythix-cli makemigrations --name initial` again to re-create your database schema.
363
+ This will create a migration in the `./app/migrations` folder to add the models `Product`, `Order`, and `LineItem`. These specified models must already exist, and be able to be loaded by `mythix` for this to work.
380
364
 
381
365
  **To run migrations**: Simply invoke the following command:
382
366
 
@@ -427,7 +411,7 @@ const {
427
411
  TaskBase,
428
412
  } = require('mythix');
429
413
 
430
- module.exports = defineTask('CustomTask', ({ application, Parent, time, Sequelize }) => {
414
+ module.exports = defineTask('CustomTask', ({ application, Parent, time }) => {
431
415
  const workerCount = application.getConfigValue('tasks.CustomTask.workers', 1, 'integer');
432
416
 
433
417
  return class CustomTask extends Parent {
@@ -457,20 +441,20 @@ module.exports = defineTask('CustomTask', ({ application, Parent, time, Sequeliz
457
441
  // ... do some task
458
442
  }
459
443
 
460
- // Optionally, you can define your own "shouldRun" method
461
- // you could use this, for example, to have your task
444
+ // Optionally, you can define your own "nextRun" method.
445
+ // You could use this, for example, to have your task
462
446
  // run at a scheduled time.
463
447
  // 'lastTime', 'currentTime', and 'diff' are in seconds
464
- // 'taskIndex' is the index of this worker
465
- // If this returns 'true', then the task will be ran
466
- // otherwise, it won't be ran until this method returns true.
467
- // This method is called every second.
468
- static shouldRun(TaskClass, taskIndex, lastTime, currentTime, diff) {
448
+ // 'taskIndex' is the index of this worker.
449
+ // This should return a Luxon DateTime object
450
+ // specifying the exact time that the task should
451
+ // run next.
452
+ static nextRun(taskIndex, lastTime, currentTime, diff) {
469
453
  if (meetsScheduledTime())
470
454
  return true;
471
455
 
472
456
  // We don't pass TaskClass here because it is bound to the method
473
- return TaskBase.shouldRun(taskIndex, lastTime, currentTime, diff);
457
+ return TaskBase.nextRun(taskIndex, lastTime, currentTime, diff);
474
458
  }
475
459
  };
476
460
  });
@@ -478,13 +462,13 @@ module.exports = defineTask('CustomTask', ({ application, Parent, time, Sequeliz
478
462
 
479
463
  ## Mythix RC
480
464
 
481
- This is the `mythix` RC file. The primary purpose of this file is to let the `mythix-cli` know how to fetch and instantiate your `Application` class. By default, `mythix-cli` will expect the application class to be exported from `./app/application.js`. If not found there, it will panic, unless you tell it how to load your mythix application.
465
+ The primary purpose of the `mythix` RC file is to let the `mythix-cli` know how to fetch and instantiate your `Application` class. By default, `mythix-cli` will expect the application class to be exported from `./app/application.js`. If not found there, it will panic, unless you tell it how to load your mythix application.
482
466
 
483
467
  `{projectRoot}/.mythix-config.js` is the location searched for to load your `mythix` RC. You can also specify a `--mythixConfig` argument to any invocation of `mythix-cli` to tell `mythix-cli` where to load this configuration file.
484
468
 
485
469
  There is only one required method that needs to be exported from `.mythix-config.js`, and it is named `getApplicationClass`. This method is expected to return your own custom `Application` class that extends from `Mythix.application`. This is all you ever really need in the mythix RC.
486
470
 
487
- However, if you want to control how your application gets instantiated, you can also optionally define and export an `async createApplication` method that will create your application. This method SHOULD NOT start your application, but simply instantiate it. This method receives two arguments: `Application`, and `options`. `Application` is the application class itself, and `options` are any options that should be passed to your `Application.constructor`.
471
+ However, if you want to control how your application gets instantiated, you can also optionally define and export an `async createApplication` method that will create your application. This method **SHOULD NOT** start your application, but simply instantiate it. This method receives two arguments: `Application`, and `options`. `Application` is the application class itself, and `options` are any options that should be passed to your `Application.constructor`.
488
472
 
489
473
  When the `mythix-cli` is invoked, it will always pass a `{ cli: true }` option to your Application class. You can use this to know if your application is running in "CLI mode".
490
474
 
@@ -579,10 +563,8 @@ Create a new model class, giving your model the name specified by the `modelName
579
563
  * `context`:
580
564
  * **`Parent`** - The parent class your model should inherit from. If no parent model class was specified as the third argument `ParentModelClassToInheritFrom`, then this defaults to `Mythix.ModelBase`.
581
565
  * **`application`** - The `mythix` application instance of the currently running application.
582
- * **`Type`** - A shortcut for `Sequelize.DataTypes`.
583
- * **`Relation`** - Relationship helpers. This is an object that contains the following methods: `hasOne`, `belongsTo`, `hasMany`, and `belongsToMany`. These methods closely mirror the association methods in `sequelize`.
584
- * **`Sequelize`** - `Sequelize` module that was loaded by `mythix`.
585
- * **`connection`** - The database connection used by the currently running `mythix` application. This is an instance of `sequelize`.
566
+ * **`Types`** - A shortcut for `MythixORM.Types`.
567
+ * **`connection`** - The database connection used by the currently running `mythix` application. This is a `mythix-orm` connection.
586
568
  * **`modelName`** - The same `modelName` string given to the call to `defineModel`.
587
569
  * *(optional)* **ParentModelClassToInheritFrom** *`<class extends Mythix.ModelBase>`* - If specified, this this will be assigned to `context.Parent`, which your model class should always extend from.
588
570
 
@@ -595,34 +577,48 @@ The return value will be a model class, inherited from `Mythix.ModelBase`.
595
577
  ```javascript
596
578
  const { defineModel } = require('mythix');
597
579
 
598
- module.exports = defineModel('Product', ({ Parent, Type, Relation }) => {
580
+ module.exports = defineModel('Product', ({ Parent, Types }) => {
599
581
  return class Product extends Parent {
600
- // Define the model fields, using Sequelize syntax (mostly)
582
+ // Define the model fields, using mythix-orm
601
583
  static fields = {
584
+ ...(Parent.fields || {}),
602
585
  id: {
603
- type: Type.UUID,
604
- defaultValue: Type.UUIDV4,
586
+ type: Types.UUIDV4,
587
+ defaultValue: Types.UUIDV4.Default.UUIDV4(),
605
588
  primaryKey: true,
606
589
  },
607
590
  name: {
608
- type: Type.STRING(32),
591
+ type: Types.STRING(32),
609
592
  allowNull: false,
610
- // "index" is not sequelize syntax... this is mythix sugar
611
- // for a simple way to define that we want to index this column
612
593
  index: true,
613
594
  },
614
595
  price: {
615
- type: Type.FLOAT,
596
+ type: Types.NUMERIC(),
616
597
  allowNull: false,
617
598
  index: true,
618
599
  },
600
+ // Relationship to "Orders" table
601
+ orders: {
602
+ type: Types.Models('Order', async ({ self }, { Order, LineItem }, userQuery) => {
603
+ // Pull distinct orders
604
+ return Order
605
+ .$.DISTINCT
606
+ // Joining the Order table with the LineItem table
607
+ // where LineItem.orderID equals Order.id
608
+ .id
609
+ .EQ(LineItem.where.orderID)
610
+ .AND
611
+ // And the line item contains this
612
+ // product id
613
+ .LineItem.productID
614
+ .EQ(self.id)
615
+ // Now tack on any extra query the
616
+ // user specified
617
+ .MERGE(userQuery);
618
+ }),
619
+ },
619
620
  };
620
621
 
621
- // Define model relations
622
- static relations = [
623
- Relation.belongsTo('Order', { allowNull: false, onDelete: 'RESTRICT', name: 'order' }),
624
- ];
625
-
626
622
  // Optionally define model methods
627
623
  // ...
628
624
  };
@@ -633,6 +629,8 @@ module.exports = defineModel('Product', ({ Parent, Type, Relation }) => {
633
629
 
634
630
  #### Description
635
631
 
632
+ **Note: This section is outdated... command arguments have been changed to use the [cmded](https://www.npmjs.com/package/cmded) module. Refer to `mythix` [built-in commands](https://github.com/th317erd/mythix/blob/main/src/cli/deploy-command.js) for examples on the new command line argument interface.**
633
+
636
634
  Create a new command class, giving your command the name specified by the `commandName` argument (all lower-case). The `definer` method will be invoked immediately upon the call to `Mythix.defineCommand`, and is expected to return a new controller class that inherits from `context.Parent`. `context.Parent` by default (if no `ParentCommandClassNameToInheritFrom` argument is specified) will be `Mythix.CommandBase`.
637
635
 
638
636
  #### Static Class Properties
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix",
3
- "version": "2.12.1",
3
+ "version": "3.0.0",
4
4
  "description": "Mythix is a NodeJS web-app framework",
5
5
  "main": "src/index",
6
6
  "scripts": {
@@ -20,11 +20,11 @@
20
20
  "homepage": "https://github.com/th317erd/mythix#readme",
21
21
  "devDependencies": {
22
22
  "@spothero/eslint-plugin-spothero": "github:spothero/eslint-plugin-spothero",
23
- "@types/node": "^18.11.10",
23
+ "@types/node": "^20.2.5",
24
24
  "colors": "^1.4.0",
25
25
  "diff": "^5.1.0",
26
- "eslint": "^8.28.0",
27
- "jasmine": "^4.5.0"
26
+ "eslint": "^8.42.0",
27
+ "jasmine": "^5.0.1"
28
28
  },
29
29
  "dependencies": {
30
30
  "@types/events": "^3.0.0",
@@ -33,9 +33,9 @@
33
33
  "express": "^4.18.2",
34
34
  "express-busboy": "github:th317erd/express-busboy#0754a570d7979097b31e48655b80d3fcd628d4e4",
35
35
  "form-data": "^4.0.0",
36
- "luxon": "^3.1.1",
36
+ "luxon": "^3.3.0",
37
37
  "micromatch": "^4.0.5",
38
- "mythix-orm": "^1.12.0",
38
+ "mythix-orm": "^1.14.1",
39
39
  "nife": "^1.12.1",
40
40
  "prompts": "^2.4.2"
41
41
  }
@@ -13,6 +13,8 @@ class ValidationError extends Error {}
13
13
  function generateMigration(migrationID, upCode, downCode) {
14
14
  let template =
15
15
  `
16
+ 'use strict';
17
+
16
18
  const MIGRATION_ID = '${migrationID}';
17
19
 
18
20
  module.exports = {
@@ -258,10 +260,17 @@ class GenerateMigrationCommand extends CommandBase {
258
260
  let field = fields[i];
259
261
  let Model = field.Model;
260
262
 
261
- // Create table
263
+ // Create column
262
264
  let createColumn = queryGenerator.generateAddColumnStatement(field, { ifNotExists: true });
263
265
  statements.push(` // Add "${Model.getTableName()}"."${field.columnName}" column\n await connection.query(\n \`${createColumn}\`,\n { logger: console },\n );`);
264
266
 
267
+ let createIndexes = queryGenerator.generateColumnIndexes(Model, field, { ifNotExists: true });
268
+ for (let j = 0, jl = createIndexes.length; j < jl; j++) {
269
+ let createIndexStatement = createIndexes[j];
270
+ statements.push(` await connection.query(\n \`${createIndexStatement.trim()}\`,\n { logger: console },\n );`);
271
+ }
272
+
273
+ // Drop column
265
274
  let dropColumn = queryGenerator.generateDropColumnStatement(field, { cascade: true, ifExists: true });
266
275
  reverseStatements.push(` // Drop "${Model.getTableName()}"."${field.columnName}" column\n await connection.query(\n \`${dropColumn.trim()}\`,\n { logger: console },\n );`);
267
276
  }
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { DateTime } = require('luxon');
4
+
3
5
  class TaskBase {
4
6
  static onTaskClassCreate(Klass) {
5
7
  return Klass;
@@ -23,18 +25,11 @@ class TaskBase {
23
25
  return startDelay;
24
26
  }
25
27
 
26
- static shouldRun(taskIndex, lastTime, currentTime, diff) {
27
- if (!lastTime) {
28
- if (diff >= this.getStartDelay(taskIndex))
29
- return true;
30
-
31
- return false;
32
- }
33
-
34
- if (diff >= this.getFrequency(taskIndex))
35
- return true;
28
+ static nextRun(taskIndex, lastTime, currentTime, diff) {
29
+ if (!lastTime)
30
+ return DateTime.now().plus({ milliseconds: this.getStartDelay(taskIndex) });
36
31
 
37
- return false;
32
+ return DateTime.now().plus({ milliseconds: Math.max(0, this.getFrequency(taskIndex) - diff) });
38
33
  }
39
34
 
40
35
  constructor(application, logger, runID) {
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const { DateTime } = require('luxon');
3
4
  const Nife = require('nife');
4
5
  const { BaseModule } = require('../modules/base-module');
5
6
  const {
@@ -132,6 +133,8 @@ class TaskModule extends BaseModule {
132
133
  successResult(result);
133
134
  } catch (error) {
134
135
  errorResult(error);
136
+ } finally {
137
+ taskInfo.nextRunAt = TaskKlass.nextRun(taskIndex, lastTime, currentTime, diff);
135
138
  }
136
139
  };
137
140
 
@@ -204,7 +207,7 @@ class TaskModule extends BaseModule {
204
207
  if (taskInfo.failedCount >= failAfterAttempts)
205
208
  return;
206
209
 
207
- if (!taskKlass.shouldRun(taskIndex, lastTime, currentTime, diff))
210
+ if (+taskInfo.nextRunAt >= DateTime.now().toMillis())
208
211
  return;
209
212
 
210
213
  taskInfo.lastTime = currentTime;
@@ -218,7 +221,7 @@ class TaskModule extends BaseModule {
218
221
  for (let taskIndex = 0; taskIndex < workers; taskIndex++) {
219
222
  let taskInfo = infoForTasks[taskIndex];
220
223
  if (!taskInfo)
221
- taskInfo = infoForTasks[taskIndex] = { failedCount: 0, promise: null, stop: false };
224
+ taskInfo = infoForTasks[taskIndex] = { failedCount: 0, promise: null, stop: false, nextRunAt: taskKlass.nextRun(taskIndex, undefined, DateTime.now()) };
222
225
 
223
226
  if (taskInfo.stop)
224
227
  continue;
@@ -92,6 +92,9 @@ class HTTPInterface {
92
92
  'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
93
93
  }, this.keysToLowerCase(this.defaultHeaders || {}), this.keysToLowerCase(requestOptions.headers || {}));
94
94
 
95
+ if (requestOptions.logger)
96
+ requestOptions.logger.log(`Making request: ${method} ${url}`);
97
+
95
98
  if (data) {
96
99
  if ((!method.match(/^(GET|HEAD)$/i) && requestOptions.data)) {
97
100
  if (data.constructor.name === 'FormData') {