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
|
|
4
|
-
|
|
5
|
-
## Heads UP!
|
|
3
|
+

|
|
6
4
|
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
143
|
-
'
|
|
144
|
-
'
|
|
145
|
-
'greet'
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
'
|
|
194
|
-
'
|
|
195
|
-
'
|
|
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
|
-
'
|
|
199
|
-
'
|
|
200
|
-
'
|
|
158
|
+
'property': 'userID',
|
|
159
|
+
'type': 'string',
|
|
160
|
+
'description': 'ID of user to greet',
|
|
161
|
+
'required': true,
|
|
201
162
|
},
|
|
202
163
|
],
|
|
203
|
-
{
|
|
204
|
-
|
|
205
|
-
'
|
|
206
|
-
'
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
+
Example:
|
|
223
184
|
|
|
224
185
|
```javascript
|
|
225
|
-
module.exports = function
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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 [
|
|
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,
|
|
251
|
+
module.exports = defineModel('Product', ({ Parent, Types }) => {
|
|
280
252
|
return class Product extends Parent {
|
|
281
|
-
// Define the model fields, using
|
|
253
|
+
// Define the model fields, using mythix-orm
|
|
282
254
|
static fields = {
|
|
255
|
+
...(Parent.fields || {}),
|
|
283
256
|
id: {
|
|
284
|
-
type:
|
|
285
|
-
defaultValue:
|
|
257
|
+
type: Types.UUIDV4,
|
|
258
|
+
defaultValue: Types.UUIDV4.Default.UUIDV4(),
|
|
286
259
|
primaryKey: true,
|
|
287
260
|
},
|
|
288
261
|
name: {
|
|
289
|
-
type:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 "
|
|
461
|
-
//
|
|
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
|
-
//
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
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) {
|
|
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.
|
|
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
|
-
|
|
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
|
-
* **`
|
|
583
|
-
* **`
|
|
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,
|
|
580
|
+
module.exports = defineModel('Product', ({ Parent, Types }) => {
|
|
599
581
|
return class Product extends Parent {
|
|
600
|
-
// Define the model fields, using
|
|
582
|
+
// Define the model fields, using mythix-orm
|
|
601
583
|
static fields = {
|
|
584
|
+
...(Parent.fields || {}),
|
|
602
585
|
id: {
|
|
603
|
-
type:
|
|
604
|
-
defaultValue:
|
|
586
|
+
type: Types.UUIDV4,
|
|
587
|
+
defaultValue: Types.UUIDV4.Default.UUIDV4(),
|
|
605
588
|
primaryKey: true,
|
|
606
589
|
},
|
|
607
590
|
name: {
|
|
608
|
-
type:
|
|
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:
|
|
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": "
|
|
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
|
}
|
|
@@ -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
|
|
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
|
}
|
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;
|
|
@@ -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') {
|