mythix 2.12.2 → 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
@@ -4,16 +4,12 @@
4
4
 
5
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.
6
6
 
7
- ## Heads UP!
8
-
9
- 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!
10
-
11
7
  ## Install
12
8
 
13
9
  To create a new empty mythix project:
14
10
 
15
11
  ```bash
16
- $ npx mythix-cli init my_project_name
12
+ $ npx mythix-cli create my_project_name
17
13
  ```
18
14
 
19
15
  Or to install directly as a dependency:
@@ -35,16 +31,16 @@ projectRoot/ ->
35
31
  |---- models/ (all model definitions)
36
32
  |---- seeders/ (database seeders)
37
33
  |---- tasks/ (reoccuring/cron-type tasks)
34
+ |---- routes/ (route definitions for your web-app)
38
35
  |---- application.js (application class definition)
39
36
  |---- index.js (entry point for your application)
40
- |---- routes.js (route definitions for your web-app)
41
- | .mythix-config.js (custom mythix RC)
42
- | package.json
37
+ |-- .mythix-config.js (custom mythix RC)
38
+ |-- package.json
43
39
  ```
44
40
 
45
41
  ## Creating the application
46
42
 
47
- 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:
48
44
 
49
45
  Example:
50
46
 
@@ -94,7 +90,7 @@ Or, you can simply invoke your own entry point:
94
90
  $ node app/index.js
95
91
  ```
96
92
 
97
- 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.
98
94
 
99
95
  ## Application configuration
100
96
 
@@ -140,132 +136,106 @@ Now, all you need to do is add your new controller to the routes:
140
136
  Simply modify `./app/routes.js` to have the following content:
141
137
 
142
138
  ```
143
- module.exports = function getRoutes() {
144
- return {
145
- 'api': {
146
- 'v1': {
147
- 'greet': [
148
- {
149
- 'methods': [ 'GET' ],
150
- 'controller': 'HelloWorld.greet',
151
- },
152
- ],
153
- },
154
- },
155
- };
156
- };
157
- ```
158
-
159
- That is it! Now you can goto `http://localhost:8001/api/v1/greet` and you will see `Hello world!` in your browser.
160
-
161
- ## Defining routes
162
-
163
- 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>`.
164
-
165
- *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`.*
166
-
167
- Params types are:
168
-
169
- * `string`
170
- * `integer`
171
- * `number`
172
- * `boolean`
173
- * `bigint`
174
-
175
- If no type is specified, then `mythix` will "guess" the type as best as it is able.
176
-
177
- Example 1:
178
-
179
- ```javascript
180
- module.exports = function getRoutes() {
181
- return {
182
- 'api': {
183
- 'v1': {
184
- // CRUD routes for "users"
185
- 'users': [
186
- // If there is a param of "/id", then capture
187
- // it and pass it to the controller
188
- '/<id:integer>': [
189
- {
190
- 'methods': [ 'GET' ],
191
- 'accept': [ 'application/json' ],
192
- 'controller': 'User.show',
193
- },
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': [
194
149
  {
195
- 'methods': [ 'PUT' ],
196
- 'accept': [ 'application/json' ],
197
- 'controller': 'User.update',
150
+ 'property': 'name',
151
+ 'type': 'string',
152
+ 'description': 'Name to use to greet the user',
153
+ 'required': true,
198
154
  },
155
+ ],
156
+ 'params': [
199
157
  {
200
- 'methods': [ 'DELETE' ],
201
- 'accept': [ 'application/json' ],
202
- 'controller': 'User.delete',
158
+ 'property': 'userID',
159
+ 'type': 'string',
160
+ 'description': 'ID of user to greet',
161
+ 'required': true,
203
162
  },
204
163
  ],
205
- {
206
- 'methods': [ 'GET' ],
207
- 'accept': [ 'application/json' ],
208
- 'controller': 'User.index',
209
- },
210
- {
211
- 'methods': [ 'POST' ],
212
- 'accept': [ 'application/json' ],
213
- 'controller': 'User.create',
214
- },
215
- ],
216
- },
217
- },
218
- };
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
+ });
219
174
  };
220
175
  ```
221
176
 
222
- 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.
223
182
 
224
- You can also be more direct:
183
+ Example:
225
184
 
226
185
  ```javascript
227
- module.exports = function getRoutes() {
228
- return {
229
- // The '?' makes this param optional
230
- // either /api/v1/users
231
- // or /api/v1/users/234
232
- // will work
233
- 'api/v1/users/<id?:integer>': [
234
- {
235
- 'methods': [ 'GET' ],
236
- 'accept': [ 'application/json' ],
237
- 'requireParams': [ 'id' ],
238
- 'controller': 'User.show',
239
- },
240
- {
241
- 'methods': [ 'PUT' ],
242
- 'accept': [ 'application/json' ],
243
- 'controller': 'User.update',
244
- },
245
- {
246
- 'methods': [ 'DELETE' ],
247
- 'accept': [ 'application/json' ],
248
- 'controller': 'User.delete',
249
- },
250
- {
251
- 'methods': [ 'GET' ],
252
- 'accept': [ 'application/json' ],
253
- 'controller': 'User.index',
254
- },
255
- {
256
- 'methods': [ 'POST' ],
257
- 'accept': [ 'application/json' ],
258
- 'controller': 'User.create',
259
- },
260
- },
261
- },
262
- };
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
+ });
263
233
  };
264
234
  ```
265
235
 
266
236
  ## Defining models
267
237
 
268
- `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.
269
239
 
270
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.
271
241
 
@@ -278,34 +248,48 @@ Example:
278
248
  ```javascript
279
249
  const { defineModel } = require('mythix');
280
250
 
281
- module.exports = defineModel('Product', ({ Parent, Type, Relation }) => {
251
+ module.exports = defineModel('Product', ({ Parent, Types }) => {
282
252
  return class Product extends Parent {
283
- // Define the model fields, using Sequelize syntax (mostly)
253
+ // Define the model fields, using mythix-orm
284
254
  static fields = {
255
+ ...(Parent.fields || {}),
285
256
  id: {
286
- type: Type.UUID,
287
- defaultValue: Type.UUIDV4,
257
+ type: Types.UUIDV4,
258
+ defaultValue: Types.UUIDV4.Default.UUIDV4(),
288
259
  primaryKey: true,
289
260
  },
290
261
  name: {
291
- type: Type.STRING(32),
262
+ type: Types.STRING(32),
292
263
  allowNull: false,
293
- // "index" is not sequelize syntax... this is mythix sugar
294
- // for a simple way to define that we want to index this column
295
264
  index: true,
296
265
  },
297
266
  price: {
298
- type: Type.FLOAT,
267
+ type: Types.NUMERIC(),
299
268
  allowNull: false,
300
269
  index: true,
301
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
+ },
302
291
  };
303
292
 
304
- // Define model relations
305
- static relations = [
306
- Relation.belongsTo('Order', { allowNull: false, onDelete: 'RESTRICT', name: 'order' }),
307
- ];
308
-
309
293
  // Optionally define model methods
310
294
  // ...
311
295
  };
@@ -370,15 +354,13 @@ $ mythix-cli deploy --target ssh://host/path/
370
354
 
371
355
  ## Migrations
372
356
 
373
- 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:
374
358
 
375
359
  ```bash
376
- $ mythix-cli makemigrations --name initial
360
+ $ mythix-cli generate migration --name new-models add models Product Order LineItem
377
361
  ```
378
362
 
379
- This will create a migration in the `./app/migrations` folder to setup the initial schema of your database.
380
-
381
- 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.
382
364
 
383
365
  **To run migrations**: Simply invoke the following command:
384
366
 
@@ -429,7 +411,7 @@ const {
429
411
  TaskBase,
430
412
  } = require('mythix');
431
413
 
432
- module.exports = defineTask('CustomTask', ({ application, Parent, time, Sequelize }) => {
414
+ module.exports = defineTask('CustomTask', ({ application, Parent, time }) => {
433
415
  const workerCount = application.getConfigValue('tasks.CustomTask.workers', 1, 'integer');
434
416
 
435
417
  return class CustomTask extends Parent {
@@ -459,20 +441,20 @@ module.exports = defineTask('CustomTask', ({ application, Parent, time, Sequeliz
459
441
  // ... do some task
460
442
  }
461
443
 
462
- // Optionally, you can define your own "shouldRun" method
463
- // 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
464
446
  // run at a scheduled time.
465
447
  // 'lastTime', 'currentTime', and 'diff' are in seconds
466
- // 'taskIndex' is the index of this worker
467
- // If this returns 'true', then the task will be ran
468
- // otherwise, it won't be ran until this method returns true.
469
- // This method is called every second.
470
- 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) {
471
453
  if (meetsScheduledTime())
472
454
  return true;
473
455
 
474
456
  // We don't pass TaskClass here because it is bound to the method
475
- return TaskBase.shouldRun(taskIndex, lastTime, currentTime, diff);
457
+ return TaskBase.nextRun(taskIndex, lastTime, currentTime, diff);
476
458
  }
477
459
  };
478
460
  });
@@ -480,13 +462,13 @@ module.exports = defineTask('CustomTask', ({ application, Parent, time, Sequeliz
480
462
 
481
463
  ## Mythix RC
482
464
 
483
- 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.
484
466
 
485
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.
486
468
 
487
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.
488
470
 
489
- 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`.
490
472
 
491
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".
492
474
 
@@ -581,10 +563,8 @@ Create a new model class, giving your model the name specified by the `modelName
581
563
  * `context`:
582
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`.
583
565
  * **`application`** - The `mythix` application instance of the currently running application.
584
- * **`Type`** - A shortcut for `Sequelize.DataTypes`.
585
- * **`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`.
586
- * **`Sequelize`** - `Sequelize` module that was loaded by `mythix`.
587
- * **`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.
588
568
  * **`modelName`** - The same `modelName` string given to the call to `defineModel`.
589
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.
590
570
 
@@ -597,34 +577,48 @@ The return value will be a model class, inherited from `Mythix.ModelBase`.
597
577
  ```javascript
598
578
  const { defineModel } = require('mythix');
599
579
 
600
- module.exports = defineModel('Product', ({ Parent, Type, Relation }) => {
580
+ module.exports = defineModel('Product', ({ Parent, Types }) => {
601
581
  return class Product extends Parent {
602
- // Define the model fields, using Sequelize syntax (mostly)
582
+ // Define the model fields, using mythix-orm
603
583
  static fields = {
584
+ ...(Parent.fields || {}),
604
585
  id: {
605
- type: Type.UUID,
606
- defaultValue: Type.UUIDV4,
586
+ type: Types.UUIDV4,
587
+ defaultValue: Types.UUIDV4.Default.UUIDV4(),
607
588
  primaryKey: true,
608
589
  },
609
590
  name: {
610
- type: Type.STRING(32),
591
+ type: Types.STRING(32),
611
592
  allowNull: false,
612
- // "index" is not sequelize syntax... this is mythix sugar
613
- // for a simple way to define that we want to index this column
614
593
  index: true,
615
594
  },
616
595
  price: {
617
- type: Type.FLOAT,
596
+ type: Types.NUMERIC(),
618
597
  allowNull: false,
619
598
  index: true,
620
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
+ },
621
620
  };
622
621
 
623
- // Define model relations
624
- static relations = [
625
- Relation.belongsTo('Order', { allowNull: false, onDelete: 'RESTRICT', name: 'order' }),
626
- ];
627
-
628
622
  // Optionally define model methods
629
623
  // ...
630
624
  };
@@ -635,6 +629,8 @@ module.exports = defineModel('Product', ({ Parent, Type, Relation }) => {
635
629
 
636
630
  #### Description
637
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
+
638
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`.
639
635
 
640
636
  #### Static Class Properties
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix",
3
- "version": "2.12.2",
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.13.2",
38
+ "mythix-orm": "^1.14.1",
39
39
  "nife": "^1.12.1",
40
40
  "prompts": "^2.4.2"
41
41
  }
@@ -260,10 +260,17 @@ class GenerateMigrationCommand extends CommandBase {
260
260
  let field = fields[i];
261
261
  let Model = field.Model;
262
262
 
263
- // Create table
263
+ // Create column
264
264
  let createColumn = queryGenerator.generateAddColumnStatement(field, { ifNotExists: true });
265
265
  statements.push(` // Add "${Model.getTableName()}"."${field.columnName}" column\n await connection.query(\n \`${createColumn}\`,\n { logger: console },\n );`);
266
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
267
274
  let dropColumn = queryGenerator.generateDropColumnStatement(field, { cascade: true, ifExists: true });
268
275
  reverseStatements.push(` // Drop "${Model.getTableName()}"."${field.columnName}" column\n await connection.query(\n \`${dropColumn.trim()}\`,\n { logger: console },\n );`);
269
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;