mythix 1.2.0 → 2.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.
Files changed (31) hide show
  1. package/package.json +4 -12
  2. package/src/application.js +5 -2
  3. package/src/cli/migrations/makemigrations-command.js +1 -2
  4. package/src/cli/shell-command.js +0 -2
  5. package/src/controllers/controller-module.js +0 -9
  6. package/src/http-server/http-server.js +0 -5
  7. package/src/models/index.js +0 -4
  8. package/src/models/migration-model.js +2 -2
  9. package/src/models/model-module.js +19 -32
  10. package/src/models/model-utils.js +24 -374
  11. package/src/models/model.js +29 -150
  12. package/src/modules/database-module.js +23 -18
  13. package/src/tasks/task-module.js +1 -11
  14. package/src/tasks/task-utils.js +1 -2
  15. package/src/utils/test-utils.js +38 -50
  16. package/.eslintrc.js +0 -94
  17. package/.vscode/settings.json +0 -7
  18. package/spec/controllers/controller-utils-spec.js +0 -145
  19. package/spec/controllers/generate-client-api-interface-spec.js +0 -156
  20. package/spec/controllers/generateClientAPIInterface01.snapshot +0 -552
  21. package/spec/controllers/generateClientAPIInterface02.snapshot +0 -465
  22. package/spec/controllers/generateClientAPIInterface03.snapshot +0 -418
  23. package/spec/controllers/generateClientAPIInterface04.snapshot +0 -552
  24. package/spec/controllers/generateClientAPIInterface05.snapshot +0 -552
  25. package/spec/support/application.js +0 -50
  26. package/spec/support/config/index.js +0 -3
  27. package/spec/support/jasmine.json +0 -13
  28. package/spec/support/snapshots.js +0 -63
  29. package/spec/utils/crypto-utils-spec.js +0 -28
  30. package/spec/utils/file-utils-spec.js +0 -14
  31. package/spec/utils/mime-utils-spec.js +0 -175
@@ -1,9 +1,21 @@
1
1
  'use strict';
2
2
 
3
- const Nife = require('nife');
4
- const { Sequelize } = require('sequelize');
3
+ const { Model: _Model } = require('mythix-orm');
4
+
5
+ class Model extends _Model {
6
+ static getModel(modelName) {
7
+ if (modelName) {
8
+ let connection = this.getConnection();
9
+ return connection.getModel(modelName);
10
+ }
11
+
12
+ return this;
13
+ }
14
+
15
+ getModel(modelName) {
16
+ return this.constructor.getModel(modelName);
17
+ }
5
18
 
6
- class Model extends Sequelize.Model {
7
19
  getApplication() {
8
20
  return this.constructor.getApplication();
9
21
  }
@@ -13,21 +25,25 @@ class Model extends Sequelize.Model {
13
25
  return application.getLogger();
14
26
  }
15
27
 
16
- getDBConnection() {
28
+ getDBConnection(connection) {
29
+ if (connection)
30
+ return connection;
31
+
17
32
  let application = this.getApplication();
18
- return application.getDBConnection();
19
- }
33
+ if (!application)
34
+ return null;
20
35
 
21
- getPrimaryKeyField() {
22
- return this.constructor.getPrimaryKeyField();
23
- }
36
+ if (typeof application.getDBConnection === 'function')
37
+ return application.getDBConnection();
24
38
 
25
- getPrimaryKeyFieldName() {
26
- return this.constructor.getPrimaryKeyFieldName();
39
+ return null;
27
40
  }
28
41
 
29
- getTableName() {
30
- return this.constructor.getTableName();
42
+ getConnection(connection) {
43
+ if (connection)
44
+ return connection;
45
+
46
+ return this.getDBConnection();
31
47
  }
32
48
 
33
49
  overrideMethod(name, newMethod) {
@@ -60,143 +76,6 @@ class Model extends Sequelize.Model {
60
76
  this.overrideMethod(name, method);
61
77
  }
62
78
  }
63
-
64
- static prepareWhereStatement(conditions) {
65
- if (Nife.isEmpty(conditions))
66
- return undefined;
67
-
68
- if (conditions._mythixQuery)
69
- return conditions;
70
-
71
- const Ops = Sequelize.Op;
72
- let finalQuery = {};
73
- let keys = Object.keys(conditions).concat(Object.getOwnPropertySymbols(conditions));
74
-
75
- for (let i = 0, il = keys.length; i < il; i++) {
76
- let key = keys[i];
77
- let value = conditions[key];
78
-
79
- if (value === undefined)
80
- continue;
81
-
82
- let name = key;
83
- let invert = false;
84
-
85
- if (typeof name === 'string' && name.charAt(0) === '!') {
86
- name = name.substring(1);
87
- invert = true;
88
- }
89
-
90
- if (typeof key === 'symbol') {
91
- finalQuery[key] = value;
92
- } else if (value === null) {
93
- finalQuery[name] = (invert) ? { [Ops.not]: value } : { [Ops.is]: value };
94
- } else if (Nife.instanceOf(value, 'number', 'string', 'boolean', 'bigint')) {
95
- finalQuery[name] = (invert) ? { [Ops.ne]: value } : { [Ops.eq]: value };
96
- } else if (Nife.instanceOf(value, 'array') && Nife.isNotEmpty(value)) {
97
- finalQuery[name] = (invert) ? { [Ops.not]: { [Ops.in]: value } } : { [Ops.in]: value };
98
- } else if (Nife.isNotEmpty(value)) {
99
- if (invert)
100
- throw new Error(`Model.prepareWhereStatement: Attempted to invert a custom matcher "${name}"`);
101
-
102
- finalQuery[name] = value;
103
- }
104
- }
105
-
106
- if (Nife.isEmpty(finalQuery))
107
- return;
108
-
109
- Object.defineProperties(finalQuery, {
110
- '_mythixQuery': {
111
- writable: false,
112
- enumberable: false,
113
- configurable: false,
114
- value: true,
115
- },
116
- });
117
-
118
- return finalQuery;
119
- }
120
-
121
- static getDefaultOrderBy() {
122
- return [ this.getPrimaryKeyFieldName() ];
123
- }
124
-
125
- static prepareQueryOptions(conditions, _order) {
126
- const Ops = Sequelize.Op;
127
- let options;
128
- let query;
129
-
130
- if (conditions && Nife.isNotEmpty(conditions.where)) {
131
- query = conditions.where;
132
- options = Object.assign({}, conditions);
133
- } else if (conditions && conditions.where !== null) {
134
- query = this.prepareWhereStatement(conditions);
135
- }
136
-
137
- let order = _order;
138
- if (!options && Nife.instanceOf(order, 'object')) {
139
- options = Object.assign({}, order);
140
- order = options.order;
141
- } else if (!options) {
142
- options = {};
143
- }
144
-
145
- if (Nife.isNotEmpty(query))
146
- options.where = query;
147
-
148
- if (!order && options.defaultOrder !== false) {
149
- if (options.order) {
150
- order = options.order;
151
- } else {
152
- if (typeof this.getDefaultOrderBy === 'function')
153
- order = this.getDefaultOrderBy();
154
- else
155
- order = [ this.getPrimaryKeyFieldName() ];
156
- }
157
- }
158
-
159
- options.order = order;
160
- if (!Object.prototype.hasOwnProperty.call(options, 'distinct'))
161
- options.distinct = true;
162
-
163
- // If no "where" clause was specified, then grab everything
164
- if (!options.where)
165
- options.where = { [ this.getPrimaryKeyFieldName() ]: { [Ops.not]: null } };
166
-
167
- if (options.debug)
168
- console.log('QUERY OPTIONS: ', options);
169
-
170
- return options;
171
- }
172
-
173
- static where(conditions) {
174
- return this.prepareWhereStatement(conditions);
175
- }
176
-
177
- static async rowCount(conditions, options) {
178
- return await this.count(this.prepareQueryOptions(conditions, options));
179
- }
180
-
181
- static async bulkUpdate(attrs, conditions) {
182
- return await this.update(attrs, this.prepareQueryOptions(conditions, { distinct: false }));
183
- }
184
-
185
- static async all(conditions, order) {
186
- return await this.findAll(this.prepareQueryOptions(conditions, order));
187
- }
188
-
189
- static async first(conditions, order) {
190
- return await this.findOne(this.prepareQueryOptions(conditions, order));
191
- }
192
-
193
- static async last(conditions, _order) {
194
- let order = _order;
195
- if (!order)
196
- order = [ [ 'createdAt', 'DESC' ] ];
197
-
198
- return await this.first(conditions, order);
199
- }
200
79
  }
201
80
 
202
81
  module.exports = {
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const Nife = require('nife');
4
- const { Sequelize } = require('sequelize');
5
- const { BaseModule } = require('../modules/base-module');
3
+ const Nife = require('nife');
4
+ const { BaseModule } = require('../modules/base-module');
5
+ const { ConnectionBase } = require('mythix-orm');
6
6
 
7
7
  class DatabaseModule extends BaseModule {
8
8
  static getModuleName() {
@@ -97,31 +97,36 @@ class DatabaseModule extends BaseModule {
97
97
  }
98
98
 
99
99
  async connectToDatabase(databaseConfig) {
100
- if (!databaseConfig) {
101
- this.getLogger().error('Error: database connection options not defined');
102
- return;
103
- }
100
+ if (!databaseConfig)
101
+ throw new Error('DatabaseModule::connectToDatabase: Database connection options not defined.');
104
102
 
105
- let sequelize = new Sequelize(databaseConfig);
106
103
  let dbConnectionString;
107
-
108
104
  if (Nife.instanceOf(databaseConfig, 'string'))
109
105
  dbConnectionString = databaseConfig;
110
106
  else
111
107
  dbConnectionString = `${databaseConfig.dialect}://${databaseConfig.host}:${databaseConfig.port || '<default port>'}/${databaseConfig.database}`;
112
108
 
113
109
  try {
114
- await sequelize.authenticate();
110
+ let app = this.getApplication();
111
+ if (typeof app.createDatabaseConnection !== 'function')
112
+ throw new Error('DatabaseModule::connectToDatabase: You must define a "createDatabaseConnection" method on your Application class.');
115
113
 
116
- this.getLogger().info(`Connection to ${dbConnectionString} has been established successfully!`);
114
+ let connection = await app.createDatabaseConnection(databaseConfig);
115
+ if (!connection)
116
+ throw new Error('DatabaseModule::connectToDatabase: Application::createDatabaseConnection must return a connection.');
117
+
118
+ if (!(connection instanceof ConnectionBase) && typeof connection === 'function' && connection.prototype instanceof ConnectionBase) {
119
+ const ConnectionKlass = connection;
120
+ connection = new ConnectionKlass(databaseConfig);
117
121
 
118
- // SQLite needs foreign keys TURNED ON
119
- // when we first connect (they default to off)
120
- let dialect = sequelize.getDialect();
121
- if (dialect === 'sqlite')
122
- await sequelize.query('PRAGMA foreign_keys = ON');
122
+ await connection.start();
123
+ } else if (!connection.isStarted()) {
124
+ await connection.start();
125
+ }
126
+
127
+ this.getLogger().info(`Connection to ${dbConnectionString} has been established successfully!`);
123
128
 
124
- return sequelize;
129
+ return connection;
125
130
  } catch (error) {
126
131
  this.getLogger().error(`Unable to connect to database ${dbConnectionString}:`, error);
127
132
  throw error;
@@ -146,7 +151,7 @@ class DatabaseModule extends BaseModule {
146
151
  return;
147
152
 
148
153
  this.getLogger().info('Closing database connections...');
149
- await this.connection.close();
154
+ await this.connection.stop();
150
155
  this.getLogger().info('All database connections closed successfully!');
151
156
  }
152
157
  }
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const Nife = require('nife');
4
- const { Sequelize } = require('sequelize');
5
4
  const { BaseModule } = require('../modules/base-module');
6
5
  const {
7
6
  fileNameWithoutExtension,
@@ -85,7 +84,7 @@ class TaskModule extends BaseModule {
85
84
  let connection = (typeof application.getDBConnection === 'function') ? application.getDBConnection() : null;
86
85
  let dbConfig = (typeof application.getDBConfig === 'function') ? application.getDBConfig() : null;
87
86
  let tasks = {};
88
- let args = { application: this, Sequelize, connection, dbConfig };
87
+ let args = { application: this, connection, dbConfig };
89
88
 
90
89
  for (let i = 0, il = taskFiles.length; i < il; i++) {
91
90
  let taskFile = taskFiles[i];
@@ -102,15 +101,6 @@ class TaskModule extends BaseModule {
102
101
  }
103
102
  }
104
103
 
105
- Object.defineProperties(tasks, {
106
- '_files': {
107
- writable: true,
108
- enumberable: false,
109
- configurable: true,
110
- value: taskFiles,
111
- },
112
- });
113
-
114
104
  return tasks;
115
105
  }
116
106
 
@@ -83,13 +83,12 @@ class TimeHelpers {
83
83
  function defineTask(taskName, definer, _parent) {
84
84
  let parentKlass = _parent || TaskBase;
85
85
 
86
- return function({ application, Sequelize, connection, dbConfig }) {
86
+ return function({ application, connection, dbConfig }) {
87
87
  let time = new TimeHelpers();
88
88
 
89
89
  let Klass = definer({
90
90
  Parent: parentKlass,
91
91
  application,
92
- Sequelize,
93
92
  connection,
94
93
  dbConfig,
95
94
  taskName,
@@ -3,21 +3,16 @@
3
3
  /* global process */
4
4
 
5
5
  const Nife = require('nife');
6
+ const { Utils } = require('mythix-orm');
6
7
  const { Logger } = require('../logger');
7
- const HTTPUtils = require('./http-utils');
8
8
  const { HTTPInterface } = require('./http-interface');
9
9
  const { DatabaseModule } = require('../modules/database-module');
10
10
  const { HTTPServerModule } = require('../http-server/http-server-module');
11
11
 
12
12
  class TestDatabaseModule extends DatabaseModule {
13
- getDatabaseConfig() {
14
- let config = super.getDatabaseConfig();
15
- return Object.assign({}, config, { dialect: 'sqlite', storage: ':memory:' });
16
- }
17
-
18
13
  getTablePrefix() {
19
14
  let prefix = super.getTablePrefix();
20
- return `${prefix.replace(/_test/g, '')}_test_`.replace(/_+/g, '_');
15
+ return `${prefix.replace(/_test/g, '')}_test_`.replace(/_+/g, '_').replace(/\W+/g, '_');
21
16
  }
22
17
  }
23
18
 
@@ -69,9 +64,10 @@ function createTestApplication(Application) {
69
64
 
70
65
  constructor(_opts) {
71
66
  let opts = Nife.extend(true, {
72
- autoReload: false,
73
- runTasks: false,
74
- testMode: true,
67
+ environment: 'test',
68
+ autoReload: false,
69
+ runTasks: false,
70
+ testMode: true,
75
71
  }, _opts || {});
76
72
 
77
73
  super(opts);
@@ -103,33 +99,53 @@ function createTestApplication(Application) {
103
99
  let result = await super.start(...args);
104
100
 
105
101
  if (typeof this.getDBConnection === 'function') {
106
- let dbConnection = this.getDBConnection();
107
- await dbConnection.sync({ force: true, logging: false });
102
+ let connection = this.getDBConnection();
103
+ await this.createAllTables(connection);
108
104
  }
109
105
 
110
106
  return result;
111
107
  }
112
108
 
109
+ async createAllTables(connection) {
110
+ const createTable = async (connection, Model, options) => {
111
+ return await connection.createTable(Model, options);
112
+ };
113
+
114
+ let models = connection.getModels();
115
+ let modelNames = Object.keys(models);
116
+
117
+ modelNames = Utils.sortModelNamesByCreationOrder(connection, modelNames);
118
+
119
+ for (let i = 0, il = modelNames.length; i < il; i++) {
120
+ let modelName = modelNames[i];
121
+ let model = models[modelName];
122
+
123
+ await createTable(connection, model, { ifNotExists: true });
124
+ }
125
+ }
126
+
113
127
  async truncateAllTables(exclude) {
114
- let dbConnection = this.getDBConnection();
115
- let models = this.getModels();
116
- let modelNames = Object.keys(models);
128
+ let connection = this.getDBConnection();
129
+ let models = this.getModels();
130
+ let modelNames = Object.keys(models);
117
131
 
118
- await dbConnection.query('PRAGMA foreign_keys = OFF');
132
+ modelNames = Utils.sortModelNamesByCreationOrder(connection, modelNames);
133
+
134
+ await connection.enableForeignKeyConstraints(false);
119
135
 
120
136
  try {
121
- for (let i = 0, il = modelNames.length; i < il; i++) {
137
+ for (let i = modelNames.length - 1; i >= 0; i--) {
122
138
  let modelName = modelNames[i];
123
139
  if (exclude && exclude.indexOf(modelName) >= 0)
124
140
  continue;
125
141
 
126
- let model = models[modelName];
127
- await dbConnection.query(`DELETE FROM "${model.tableName}"`);
142
+ let Model = models[modelName];
143
+ await connection.truncate(Model);
128
144
  }
129
145
  } catch (error) {
130
146
  console.error('TRUNCATE ERROR: ', error);
131
147
  } finally {
132
- await dbConnection.query('PRAGMA foreign_keys = ON');
148
+ await connection.enableForeignKeyConstraints(true);
133
149
  }
134
150
  }
135
151
 
@@ -140,36 +156,8 @@ function createTestApplication(Application) {
140
156
  try {
141
157
  let Klass = callback.call(this, OriginalModel);
142
158
 
143
- const modelConverter = (model) => {
144
- if (model == null)
145
- return model;
146
-
147
- if (model instanceof Array)
148
- return model.map(modelConverter);
149
-
150
- // Do a direct assign to "dataValues"
151
- // "set" modifies the id
152
- let newModelInstance = new Klass();
153
- Object.assign(newModelInstance.dataValues, model.dataValues);
154
-
155
- return newModelInstance;
156
- };
157
-
158
- const modelStaticBind = (name) => {
159
- const originalMethod = Klass[name];
160
-
161
- return (async function(...args) {
162
- let results = await originalMethod.call(Klass, ...args);
163
- return modelConverter(results);
164
- }).bind(Klass);
165
- };
166
-
167
- Klass.where = modelStaticBind('where');
168
- Klass.all = modelStaticBind('all');
169
- Klass.first = modelStaticBind('first');
170
- Klass.last = modelStaticBind('last');
171
-
172
- models[modelName] = Klass;
159
+ let connection = this.getDBConnection();
160
+ connection.registerModel(Klass);
173
161
 
174
162
  return await runner.call(this, Klass);
175
163
  } finally {
package/.eslintrc.js DELETED
@@ -1,94 +0,0 @@
1
- 'use strict';
2
-
3
- /* eslint-disable no-magic-numbers */
4
-
5
- module.exports = {
6
- 'env': {
7
- 'browser': true,
8
- 'commonjs': true,
9
- 'es2021': true,
10
- },
11
- 'extends': [
12
- 'eslint:recommended',
13
- ],
14
- 'parserOptions': {
15
- 'ecmaVersion': 'latest',
16
- },
17
- 'plugins': [
18
- '@spothero/eslint-plugin-spothero',
19
- ],
20
- 'rules': {
21
- '@spothero/spothero/ternary-parentheses': 'error',
22
- 'arrow-parens': 'error',
23
- 'arrow-spacing': [ 'error', { before: true, after: true } ],
24
- 'block-scoped-var': 'warn',
25
- 'block-spacing': 'error',
26
- 'brace-style': [ 'error', '1tbs' ],
27
- 'camelcase': 'warn',
28
- 'comma-dangle': [ 'error', 'always-multiline' ],
29
- 'comma-spacing': [ 'error', { before: false, after: true } ],
30
- 'comma-style': [ 'error', 'last' ],
31
- 'curly': [ 'error', 'multi-or-nest', 'consistent' ],
32
- 'default-case-last': 'error',
33
- 'default-param-last': 'error',
34
- 'eqeqeq': [ 'error', 'smart' ],
35
- 'func-call-spacing': [ 'error', 'never' ],
36
- 'guard-for-in': 'error',
37
- 'implicit-arrow-linebreak': [ 'error', 'beside' ],
38
- 'indent': [ 'error', 2, { 'SwitchCase': 1 } ],
39
- 'jsx-quotes': [ 'error', 'prefer-double' ],
40
- 'key-spacing': [ 'error', { beforeColon: false, afterColon: true, mode: 'minimum', 'align': 'value' } ],
41
- 'keyword-spacing': [ 'error', { before: true, after: true } ],
42
- 'linebreak-style': [ 'error', 'unix' ],
43
- 'lines-between-class-members': 'error',
44
- 'max-classes-per-file': [ 'error', 3 ],
45
- 'new-cap': [ 'error', { 'properties': false } ],
46
- 'new-parens': 'error',
47
- 'no-array-constructor': 'warn',
48
- 'no-caller': 'error',
49
- 'no-confusing-arrow': 'error',
50
- 'no-empty': 'warn',
51
- 'no-eq-null': 0,
52
- 'no-eval': 'error',
53
- 'no-extend-native': 'error',
54
- 'no-extra-label': 'error',
55
- 'no-floating-decimal': 'error',
56
- 'no-global-assign': 'error',
57
- 'no-implied-eval': 'error',
58
- 'no-labels': 'error',
59
- 'no-lone-blocks': 'warn',
60
- 'no-loop-func': 0,
61
- 'no-magic-numbers': [ 'warn', { ignoreArrayIndexes: true, ignoreDefaultValues: true, ignore: [ -1, 0, 1, 2, 16, 32, 64, 128, 256, 1024, 2048, 200, 301, 302, 400, 401, 403, 404, 500 ] } ],
62
- 'no-nested-ternary': 'error',
63
- 'no-param-reassign': 'error',
64
- 'no-promise-executor-return': 'error',
65
- 'no-return-assign': 'error',
66
- 'no-sequences': 'error',
67
- 'no-shadow': 0,
68
- 'no-throw-literal': 'warn',
69
- 'no-trailing-spaces': 'error',
70
- 'no-unmodified-loop-condition': 'warn',
71
- 'no-unreachable-loop': 'warn',
72
- 'no-unreachable': 'warn',
73
- 'no-unused-private-class-members': 'warn',
74
- 'no-unused-vars': 'warn',
75
- 'no-whitespace-before-property': 'error',
76
- 'nonblock-statement-body-position': [ 'error', 'below' ],
77
- 'one-var': [ 'error', 'never' ],
78
- 'quotes': [ 'error', 'single' ],
79
- 'radix': 'error',
80
- 'rest-spread-spacing': [ 'error', 'never' ],
81
- 'semi-spacing': [ 'error', { before: false, after: true } ],
82
- 'semi-style': [ 'error', 'last' ],
83
- 'semi': 'error',
84
- 'space-before-blocks': 'error',
85
- 'space-infix-ops': 'error',
86
- 'space-unary-ops': [ 'error', { words: false, nonwords: false } ],
87
- 'strict': 'error',
88
- 'switch-colon-spacing': [ 'error', { before: false, after: true } ],
89
- 'template-curly-spacing': 'error',
90
- 'template-tag-spacing': 'error',
91
- 'wrap-iife': [ 'error', 'inside' ],
92
- 'yoda': 'error',
93
- },
94
- };
@@ -1,7 +0,0 @@
1
- {
2
- "workbench.colorCustomizations": {
3
- "activityBar.background": "#5F0A17",
4
- "titleBar.activeBackground": "#840E20",
5
- "titleBar.activeForeground": "#FFFBFC"
6
- }
7
- }