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 +162 -166
- package/package.json +6 -6
- package/src/cli/generators/migration-generator.js +8 -1
- package/src/tasks/task-base.js +6 -11
- package/src/tasks/task-module.js +5 -2
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
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
145
|
-
'
|
|
146
|
-
'
|
|
147
|
-
'greet'
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
'
|
|
196
|
-
'
|
|
197
|
-
'
|
|
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
|
-
'
|
|
201
|
-
'
|
|
202
|
-
'
|
|
158
|
+
'property': 'userID',
|
|
159
|
+
'type': 'string',
|
|
160
|
+
'description': 'ID of user to greet',
|
|
161
|
+
'required': true,
|
|
203
162
|
},
|
|
204
163
|
],
|
|
205
|
-
{
|
|
206
|
-
|
|
207
|
-
'
|
|
208
|
-
'
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
+
Example:
|
|
225
184
|
|
|
226
185
|
```javascript
|
|
227
|
-
module.exports = function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
{
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
'
|
|
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 [
|
|
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,
|
|
251
|
+
module.exports = defineModel('Product', ({ Parent, Types }) => {
|
|
282
252
|
return class Product extends Parent {
|
|
283
|
-
// Define the model fields, using
|
|
253
|
+
// Define the model fields, using mythix-orm
|
|
284
254
|
static fields = {
|
|
255
|
+
...(Parent.fields || {}),
|
|
285
256
|
id: {
|
|
286
|
-
type:
|
|
287
|
-
defaultValue:
|
|
257
|
+
type: Types.UUIDV4,
|
|
258
|
+
defaultValue: Types.UUIDV4.Default.UUIDV4(),
|
|
288
259
|
primaryKey: true,
|
|
289
260
|
},
|
|
290
261
|
name: {
|
|
291
|
-
type:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 "
|
|
463
|
-
//
|
|
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
|
-
//
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
static
|
|
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.
|
|
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
|
-
|
|
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
|
-
* **`
|
|
585
|
-
* **`
|
|
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,
|
|
580
|
+
module.exports = defineModel('Product', ({ Parent, Types }) => {
|
|
601
581
|
return class Product extends Parent {
|
|
602
|
-
// Define the model fields, using
|
|
582
|
+
// Define the model fields, using mythix-orm
|
|
603
583
|
static fields = {
|
|
584
|
+
...(Parent.fields || {}),
|
|
604
585
|
id: {
|
|
605
|
-
type:
|
|
606
|
-
defaultValue:
|
|
586
|
+
type: Types.UUIDV4,
|
|
587
|
+
defaultValue: Types.UUIDV4.Default.UUIDV4(),
|
|
607
588
|
primaryKey: true,
|
|
608
589
|
},
|
|
609
590
|
name: {
|
|
610
|
-
type:
|
|
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:
|
|
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": "
|
|
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": "^
|
|
23
|
+
"@types/node": "^20.2.5",
|
|
24
24
|
"colors": "^1.4.0",
|
|
25
25
|
"diff": "^5.1.0",
|
|
26
|
-
"eslint": "^8.
|
|
27
|
-
"jasmine": "^
|
|
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.
|
|
36
|
+
"luxon": "^3.3.0",
|
|
37
37
|
"micromatch": "^4.0.5",
|
|
38
|
-
"mythix-orm": "^1.
|
|
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
|
|
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
|
}
|
package/src/tasks/task-base.js
CHANGED
|
@@ -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
|
|
27
|
-
if (!lastTime)
|
|
28
|
-
|
|
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
|
|
32
|
+
return DateTime.now().plus({ milliseconds: Math.max(0, this.getFrequency(taskIndex) - diff) });
|
|
38
33
|
}
|
|
39
34
|
|
|
40
35
|
constructor(application, logger, runID) {
|
package/src/tasks/task-module.js
CHANGED
|
@@ -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 (
|
|
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;
|