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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "Mythix is a NodeJS web-app framework",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -19,26 +19,18 @@
19
19
  },
20
20
  "homepage": "https://github.com/th317erd/mythix#readme",
21
21
  "devDependencies": {
22
+ "@spothero/eslint-plugin-spothero": "github:spothero/eslint-plugin-spothero",
23
+ "eslint": "^8.13.0",
22
24
  "colors": "^1.4.0",
23
25
  "diff": "^5.1.0",
24
26
  "jasmine": "^4.0.2"
25
27
  },
26
28
  "dependencies": {
27
- "@spothero/eslint-plugin-spothero": "github:spothero/eslint-plugin-spothero",
28
29
  "chokidar": "^3.5.3",
29
- "deep-diff": "^1.0.2",
30
- "eslint": "^8.13.0",
31
30
  "express": "^4.17.3",
32
31
  "express-busboy": "^8.0.2",
33
32
  "form-data": "^4.0.0",
34
- "inflection": "^1.13.2",
35
- "lodash": "^4.17.21",
36
33
  "nife": "^1.8.1",
37
- "object-hash": "^3.0.0",
38
- "pg": "^8.7.3",
39
- "pg-hstore": "^2.3.4",
40
- "prompts": "^2.4.2",
41
- "sequelize": "^6.18.0",
42
- "sqlite3": "^5.0.9"
34
+ "prompts": "^2.4.2"
43
35
  }
44
36
  }
@@ -70,6 +70,7 @@ class Application extends EventEmitter {
70
70
  let ROOT_PATH = (_opts && _opts.rootPath) ? _opts.rootPath : Path.resolve(__dirname);
71
71
 
72
72
  let opts = Nife.extend(true, {
73
+ environment: (process.env.NODE_ENV || 'development'),
73
74
  appName: this.constructor.APP_NAME,
74
75
  rootPath: ROOT_PATH,
75
76
  configPath: Path.resolve(ROOT_PATH, 'config'),
@@ -81,7 +82,7 @@ class Application extends EventEmitter {
81
82
  commandsPath: Path.resolve(ROOT_PATH, 'commands'),
82
83
  tasksPath: Path.resolve(ROOT_PATH, 'tasks'),
83
84
  modules: this.constructor.getDefaultModules(),
84
- autoReload: (process.env.NODE_ENV || 'development') === 'development',
85
+ autoReload: ((_opts && _opts.environment) || process.env.NODE_ENV || 'development') === 'development',
85
86
  exitOnShutdown: null,
86
87
  runTasks: true,
87
88
  testMode: false,
@@ -229,8 +230,10 @@ class Application extends EventEmitter {
229
230
 
230
231
  loadConfig(configPath) {
231
232
  try {
233
+ let appOptions = this.getOptions();
234
+
232
235
  const config = require(configPath);
233
- return wrapConfig(config);
236
+ return wrapConfig(Object.assign({}, config || {}, { environment: (appOptions.environment || config.environment || 'development')}));
234
237
  } catch (error) {
235
238
  this.getLogger().error(`Error while trying to load application configuration ${configPath}: `, error);
236
239
  throw error;
@@ -138,7 +138,7 @@ module.exports = defineCommand('makemigrations', ({ Parent }) => {
138
138
 
139
139
  // Now build all model information
140
140
  connection.modelManager.forEachModel((Model) => {
141
- let modelName = Model.customName || Model.name;
141
+ let modelName = Model.getModelName();
142
142
  let tableName = Model.getTableName(options);
143
143
 
144
144
  models.push({
@@ -799,7 +799,6 @@ module.exports = defineCommand('makemigrations', ({ Parent }) => {
799
799
 
800
800
  let oldAssertTableHasColumn = queryInterface.assertTableHasColumn;
801
801
  try {
802
- // Tell Sequelize to shut up
803
802
  queryInterface.assertTableHasColumn = async () => ({ [oldColumnName]: columnDefinition });
804
803
 
805
804
  return await queryInterface.renameColumn(tableName, oldColumnName, newColumnName, options);
@@ -5,7 +5,6 @@ const OS = require('os');
5
5
  const Path = require('path');
6
6
  const REPL = require('repl');
7
7
  const UUIDV4 = require('uuid').v4;
8
- const { Sequelize } = require('sequelize');
9
8
  const { defineCommand } = require('./cli-utils');
10
9
  const {
11
10
  HTTPInterface,
@@ -55,7 +54,6 @@ module.exports = defineCommand('shell', ({ Parent }) => {
55
54
  interactiveShell.setupHistory(Path.join(OS.homedir(), `.${appName}-${environment}-history`), () => {});
56
55
 
57
56
  interactiveShell.context.UUIDV4 = UUIDV4;
58
- interactiveShell.context.Sequelize = Sequelize;
59
57
  interactiveShell.context.connection = (typeof application.getDBConnection === 'function') ? application.getDBConnection() : null;
60
58
  interactiveShell.context.application = application;
61
59
  interactiveShell.context.Nife = Nife;
@@ -90,15 +90,6 @@ class ControllerModule extends BaseModule {
90
90
  }
91
91
  }
92
92
 
93
- Object.defineProperties(controllers, {
94
- '_files': {
95
- writable: true,
96
- enumberable: false,
97
- configurable: true,
98
- value: controllerFiles,
99
- },
100
- });
101
-
102
93
  return controllers;
103
94
  }
104
95
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  /* global process */
4
4
 
5
- const OS = require('os');
6
5
  const Path = require('path');
7
6
  const FileSystem = require('fs');
8
7
  const HTTP = require('http');
@@ -10,7 +9,6 @@ const HTTPS = require('https');
10
9
  const Nife = require('nife');
11
10
  const Express = require('express');
12
11
  const ExpressBusBoy = require('express-busboy');
13
- const Sequelize = require('sequelize');
14
12
 
15
13
  const {
16
14
  HTTPBaseError,
@@ -132,9 +130,6 @@ class HTTPServer {
132
130
  if (!logger)
133
131
  logger = request.mythixLogger = this.createRequestLogger(application, request);
134
132
 
135
- if (!request.Sequelize)
136
- request.Sequelize = Sequelize;
137
-
138
133
  request.route = route;
139
134
  request.params = params;
140
135
 
@@ -5,14 +5,10 @@ const { ModelModule } = require('./model-module');
5
5
 
6
6
  const {
7
7
  defineModel,
8
- getModelPrimaryKeyField,
9
- buildModelRelations,
10
8
  } = require('./model-utils');
11
9
 
12
10
  module.exports = {
13
- buildModelRelations,
14
11
  defineModel,
15
- getModelPrimaryKeyField,
16
12
  Model,
17
13
  ModelModule,
18
14
  };
@@ -4,11 +4,11 @@ const { defineModel } = require('./model-utils');
4
4
 
5
5
  const ID_STRING_MAX_SIZE = 15;
6
6
 
7
- module.exports = defineModel('Migration', ({ Parent, Type }) => {
7
+ module.exports = defineModel('Migration', ({ Parent, Types }) => {
8
8
  return class Migration extends Parent {
9
9
  static fields = {
10
10
  id: {
11
- type: Type.STRING(ID_STRING_MAX_SIZE),
11
+ type: Types.STRING(ID_STRING_MAX_SIZE),
12
12
  allowNull: false,
13
13
  primaryKey: true,
14
14
  index: true,
@@ -2,10 +2,8 @@
2
2
 
3
3
  /* global __dirname */
4
4
 
5
- const Path = require('path');
6
- const { Sequelize } = require('sequelize');
7
- const { BaseModule } = require('../modules/base-module');
8
- const { buildModelRelations } = require('./model-utils');
5
+ const Path = require('path');
6
+ const { BaseModule } = require('../modules/base-module');
9
7
  const {
10
8
  fileNameWithoutExtension,
11
9
  walkDir,
@@ -28,15 +26,6 @@ class ModelModule extends BaseModule {
28
26
  constructor(application) {
29
27
  super(application);
30
28
 
31
- Object.defineProperties(this, {
32
- 'models': {
33
- writable: true,
34
- enumerable: false,
35
- configurable: true,
36
- value: {},
37
- },
38
- });
39
-
40
29
  // Inject methods into the application
41
30
  Object.defineProperties(application, {
42
31
  'getModel': {
@@ -59,8 +48,7 @@ class ModelModule extends BaseModule {
59
48
  }
60
49
 
61
50
  async fileWatcherHandler(options) {
62
- let models = await this.loadModels(options.modelsPath);
63
- this.models = models;
51
+ await this.loadModels(options.modelsPath);
64
52
  }
65
53
 
66
54
  getModelFilePaths(modelsPath) {
@@ -91,7 +79,7 @@ class ModelModule extends BaseModule {
91
79
  let dbConfig = (typeof application.getDBConfig === 'function') ? application.getDBConfig() : null;
92
80
  let modelFiles = this.getModelFilePaths(modelsPath);
93
81
  let models = {};
94
- let args = { application, Sequelize, connection, dbConfig };
82
+ let args = { application, connection, dbConfig };
95
83
 
96
84
  for (let i = 0, il = modelFiles.length; i < il; i++) {
97
85
  let modelFile = modelFiles[i];
@@ -108,32 +96,31 @@ class ModelModule extends BaseModule {
108
96
  }
109
97
  }
110
98
 
111
- buildModelRelations(models);
112
-
113
- Object.defineProperties(models, {
114
- '_files': {
115
- writable: true,
116
- enumberable: false,
117
- configurable: true,
118
- value: modelFiles,
119
- },
120
- });
99
+ models = connection.registerModels(models);
121
100
 
122
101
  return models;
123
102
  }
124
103
 
125
- getModel(name) {
126
- let models = this.models;
127
- return models[name];
104
+ getModel(modelName) {
105
+ let application = this.getApplication();
106
+ let connection = (typeof application.getDBConnection === 'function') ? application.getDBConnection() : null;
107
+ if (!connection)
108
+ return;
109
+
110
+ return connection.getModel(modelName);
128
111
  }
129
112
 
130
113
  getModels() {
131
- return this.models || {};
114
+ let application = this.getApplication();
115
+ let connection = (typeof application.getDBConnection === 'function') ? application.getDBConnection() : null;
116
+ if (!connection)
117
+ return;
118
+
119
+ return connection.getModels();
132
120
  }
133
121
 
134
122
  async start(options) {
135
- let models = await this.loadModels(options.modelsPath);
136
- this.models = models;
123
+ await this.loadModels(options.modelsPath);
137
124
  }
138
125
 
139
126
  async stop() {
@@ -1,398 +1,48 @@
1
1
  'use strict';
2
2
 
3
- const Nife = require('nife');
4
- const Inflection = require('inflection');
5
- const { Model } = require('./model');
6
-
7
- const MILLISECONDS_PER_SECOND = 1000;
8
-
9
- function relationHelper(modelName, type) {
10
- return function(target, _options) {
11
- const getName = () => {
12
- if (options.name)
13
- return options.name;
14
-
15
- if (type.match(/many/i))
16
- return Inflection.pluralize(target.toLowerCase());
17
- else
18
- return target.toLowerCase();
19
- };
20
-
21
- // const getFieldName = () => {
22
- // if (options.field)
23
- // return options.field;
24
-
25
- // if (type.match(/many/i))
26
- // return Inflection.pluralize(target.toLowerCase());
27
- // else
28
- // return target.toLowerCase();
29
- // };
30
-
31
- let options = _options || {};
32
- let defaultOnDeleteAction = 'RESTRICT';
33
- if (options.allowNull)
34
- defaultOnDeleteAction = 'SET NULL';
35
-
36
- return Object.assign({}, options, {
37
- type,
38
- target,
39
- onDelete: options.onDelete || defaultOnDeleteAction,
40
- onUpdate: options.onUpdate || options.onDelete || defaultOnDeleteAction,
41
- field: options.field,
42
- name: getName(),
43
- allowNull: (Object.prototype.hasOwnProperty.call(options, 'allowNull')) ? options.allowNull : true,
44
- });
45
- };
46
- }
47
-
48
- const RELATION_HELPERS = [
49
- 'hasOne',
50
- 'belongsTo',
51
- 'hasMany',
52
- 'belongsToMany',
53
- ];
54
-
55
- function getRelationHelpers(modelName) {
56
- let obj = {};
57
-
58
- for (let i = 0, il = RELATION_HELPERS.length; i < il; i++) {
59
- let type = RELATION_HELPERS[i];
60
- obj[type] = relationHelper(modelName, type);
61
- }
62
-
63
- return obj;
64
- }
65
-
66
- function preciseNow() {
67
- let janFirst2022 = 1640995200000;
68
- let now = Date.now() - janFirst2022;
69
- let highResolutionNow = Nife.now();
70
- let diff = Math.floor(highResolutionNow);
71
-
72
- return Math.floor((now + (highResolutionNow - diff)) * MILLISECONDS_PER_SECOND);
73
- }
3
+ const Nife = require('nife');
4
+ const { Types } = require('mythix-orm');
5
+ const { Model: ModelBase } = require('./model');
74
6
 
75
7
  function defineModel(modelName, definer, _parent) {
76
- function compileModelFields(Klass, DataTypes, application, connection) {
77
- const createAutoIncrementor = () => {
78
- return () => preciseNow();
79
- };
80
-
81
- let fields = Klass.fields;
82
- let fieldNames = Object.keys(fields);
83
- let isSQLIte = !!('' + Nife.get(connection, 'options.dialect')).match(/sqlite/);
84
-
85
- for (let i = 0, il = fieldNames.length; i < il; i++) {
86
- let fieldName = fieldNames[i];
87
- let field = fields[fieldName];
88
-
89
- if (!field.field) {
90
- let columnName = Nife.camelCaseToSnakeCase(fieldName);
91
- field.field = columnName;
92
- }
93
-
94
- if (!Object.prototype.hasOwnProperty.call(field, 'allowNull'))
95
- field.allowNull = (field.primaryKey) ? false : true;
96
-
97
- // If using SQLite, which doesn't support autoincrement
98
- // on anything except the primary key, then create our
99
- // own auto-incrementor for this field
100
- if (field.autoIncrement && isSQLIte && !field.primaryKey) {
101
- application.getLogger().warn(`!Warning!: Using an auto-increment field in SQLite on a non-primary-key column "${field.field}"! Be aware that this functionality is now emulated using high resolution timestamps. This won't work unless the column is a BIGINT. You may run into serious problems with this emulation!`);
102
- field.defaultValue = createAutoIncrementor();
103
- }
104
-
105
- if (field.type === DataTypes.BIGINT) {
106
- if (!field.get) {
107
- field.get = function(name) {
108
- let value = this.getDataValue(name);
109
- if (value == null)
110
- return null;
111
-
112
- return BigInt(value);
113
- };
114
- }
115
-
116
- if (!field.set) {
117
- field.set = function(_value, name) {
118
- let value = _value;
119
- if (value == null)
120
- value = null;
121
- else
122
- value = BigInt(value);
123
-
124
- return this.setDataValue(name, value);
125
- };
126
- }
127
- }
128
- }
129
-
130
- return fields;
131
- }
132
-
133
- function cleanModelFields(Klass, connection) {
134
- let finalFields = {};
135
- let fields = Klass.fields;
136
- let fieldNames = Object.keys(fields);
137
- let isSQLIte = !!('' + Nife.get(connection, 'options.dialect')).match(/sqlite/);
138
-
139
- for (let i = 0, il = fieldNames.length; i < il; i++) {
140
- let fieldName = fieldNames[i];
141
- let field = fields[fieldName];
142
- let newField = Nife.extend(Nife.extend.FILTER, (key) => {
143
- if (key.match(/^(index)$/))
144
- return false;
145
-
146
- // Strip "autoIncrement" if this is not the primary key
147
- // and we are using sqlite for our dialect
148
- if (key === 'autoIncrement' && isSQLIte && !field.primaryKey)
149
- return false;
150
-
151
- return true;
152
- }, {}, field);
153
-
154
- finalFields[fieldName] = newField;
155
- }
156
-
157
- return finalFields;
158
- }
159
-
160
- function generateIndexes(Klass) {
161
- let finalIndexes = [];
162
- let fields = Klass.fields;
163
- let fieldNames = Object.keys(fields);
164
-
165
- for (let i = 0, il = fieldNames.length; i < il; i++) {
166
- let fieldName = fieldNames[i];
167
- let field = fields[fieldName];
168
-
169
- if (field.index) {
170
- if (field.index === 'unique') {
171
- finalIndexes.push({
172
- unique: true,
173
- fields: [ field.field ],
174
- });
175
- } else {
176
- finalIndexes.push({
177
- unique: false,
178
- fields: [ field.field ],
179
- });
180
- }
181
- }
182
- }
183
-
184
- finalIndexes = finalIndexes.concat(Klass.indexes || [], [
185
- {
186
- unique: false,
187
- fields: [ 'created_at' ],
188
- },
189
- {
190
- unique: false,
191
- fields: [ 'updated_at' ],
192
- },
193
- ]);
194
-
195
- return finalIndexes;
196
- }
197
-
198
- return function({ application, Sequelize, connection }) {
8
+ return function({ application, connection }) {
199
9
  let definerArgs = {
200
- Parent: (_parent) ? _parent : Model,
201
- Type: Sequelize.DataTypes,
202
- Relation: getRelationHelpers(modelName),
203
- Sequelize,
10
+ Parent: (_parent) ? _parent : ModelBase,
11
+ Types,
204
12
  connection,
205
13
  modelName,
206
14
  application,
207
15
  };
208
16
 
209
- let Klass = definer(definerArgs);
210
-
211
- Klass.customName = modelName;
212
-
213
- if (typeof Klass.onModelClassCreate === 'function')
214
- Klass = Klass.onModelClassCreate(Klass, definerArgs);
17
+ let Model = definer(definerArgs);
215
18
 
216
- let pluralName = (Klass.pluralName) ? Klass.pluralName : Inflection.pluralize(modelName);
217
- if (Klass.pluralName !== pluralName)
218
- Klass.pluralName = pluralName;
19
+ if (typeof Model.onModelClassCreate === 'function')
20
+ Model = Model.onModelClassCreate(Model, definerArgs);
219
21
 
220
- Klass.fields = compileModelFields(Klass, Sequelize.DataTypes, application, connection);
22
+ let tableName = Model.getTableName();
23
+ let tablePrefix = application.getDBTablePrefix();
221
24
 
222
- let indexes = generateIndexes(Klass);
25
+ if (tablePrefix)
26
+ tableName = (`${tablePrefix}${tableName}`);
223
27
 
224
- Klass.fields = cleanModelFields(Klass, connection);
28
+ Model.getTableName = () => tableName;
29
+ Model.getModelName = () => modelName;
30
+ Model.getApplication = () => application;
31
+ Model.getLogger = () => application.getLogger();
32
+ Model._getConnection = (_connection) => {
33
+ if (_connection)
34
+ return _connection;
225
35
 
226
- let applicationOptions = application.getOptions();
227
- let tableName = Klass.tableName;
228
-
229
- if (!tableName)
230
- tableName = (`${Nife.get(applicationOptions, 'database.tablePrefix', '')}${Nife.camelCaseToSnakeCase(pluralName)}`).toLowerCase();
231
-
232
- // Sequelize bullshit...
233
- // Sequelize has undocumented static hook methods
234
- // that don't align with their own hook system.
235
- // Who knows what they are for, as they aren't
236
- // documented, nor even called when statically
237
- // defined on a model class... but... there you
238
- // have it, stupid undocumented bullshit (again)
239
- // that is breaking the world.
240
- // So we must first check that each method isn't
241
- // Sequelize bullshit, otherwise we get errors.
242
- let staticHooks = {
243
- beforeBulkCreate: Klass.beforeBulkCreate,
244
- beforeBulkDestroy: Klass.beforeBulkDestroy,
245
- beforeBulkUpdate: Klass.beforeBulkUpdate,
246
- beforeValidate: Klass.beforeValidate,
247
- afterValidate: Klass.afterValidate,
248
- validationFailed: Klass.validationFailed,
249
- beforeCreate: Klass.beforeCreate,
250
- beforeDestroy: Klass.beforeDestroy,
251
- beforeUpdate: Klass.beforeUpdate,
252
- beforeSave: Klass.beforeSave,
253
- beforeUpsert: Klass.beforeUpsert,
254
- afterCreate: Klass.afterCreate,
255
- afterDestroy: Klass.afterDestroy,
256
- afterUpdate: Klass.afterUpdate,
257
- afterSave: Klass.afterSave,
258
- afterUpsert: Klass.afterUpsert,
259
- afterBulkCreate: Klass.afterBulkCreate,
260
- afterBulkDestroy: Klass.afterBulkDestroy,
261
- afterBulkUpdate: Klass.afterBulkUpdate,
36
+ return connection;
262
37
  };
263
- let hookNames = Object.keys(staticHooks);
264
- let hooks = {};
265
- for (let i = 0, il = hookNames.length; i < il; i++) {
266
- let hookName = hookNames[i];
267
- let hookFunc = staticHooks[hookName];
268
- if (typeof hookFunc !== 'function')
269
- continue;
270
-
271
- // Are you mysterious Sequelize stupidity?
272
- // If so, skip the bullshit please.
273
- if (hookFunc === Sequelize.Model[hookName])
274
- continue;
275
38
 
276
- hooks[hookName] = hookFunc;
277
- }
278
-
279
- Klass.init(Klass.fields, {
280
- underscored: true,
281
- freezeTableName: true,
282
- sequelize: connection,
283
- tableName,
284
- modelName,
285
- indexes,
286
- name: {
287
- singular: modelName.toLowerCase(),
288
- plural: Inflection.pluralize(modelName.toLowerCase()),
289
- },
290
- hooks,
291
- });
39
+ if (typeof Model.onModelClassFinalized === 'function')
40
+ Model = Model.onModelClassFinalized(Model, definerArgs);
292
41
 
293
- Klass.getApplication = () => application;
294
- Klass.getLogger = () => application.getLogger();
295
- Klass.getModelName = (function() {
296
- return modelName;
297
- }).bind(Klass);
298
-
299
- Klass.prototype.getModelName = function() {
300
- return modelName;
301
- };
302
-
303
- Klass.getPrimaryKeyField = getModelPrimaryKeyField.bind(this, Klass);
304
- Klass.getPrimaryKeyFieldName = () => (getModelPrimaryKeyField(Klass).field);
305
-
306
- if (typeof Klass.onModelClassFinalized === 'function')
307
- Klass = Klass.onModelClassFinalized(Klass, definerArgs);
308
-
309
- return { [modelName]: Klass };
42
+ return { [modelName]: Model };
310
43
  };
311
44
  }
312
45
 
313
- function getModelPrimaryKeyField(Klass) {
314
- let fields = Klass.fields;
315
- let fieldNames = Object.keys(fields);
316
-
317
- for (let i = 0, il = fieldNames.length; i < il; i++) {
318
- let fieldName = fieldNames[i];
319
- let field = fields[fieldName];
320
-
321
- if (field.primaryKey)
322
- return field;
323
- }
324
- }
325
-
326
- function buildModelRelations(models) {
327
- let modelNames = Object.keys(models);
328
- for (let i = 0, il = modelNames.length; i < il; i++) {
329
- let modelName = modelNames[i];
330
- let model = models[modelName];
331
- let relations = model.relations;
332
-
333
- if (!relations)
334
- continue;
335
-
336
- for (let j = 0, jl = relations.length; j < jl; j++) {
337
- let relation = relations[j];
338
- let type = relation.type;
339
- let fieldName = Nife.camelCaseToSnakeCase(relation.field);
340
- let targetModelName = relation.target;
341
- let targetModel = models[targetModelName];
342
- let belongsType = !!type.match(/^belongs/);
343
-
344
- if (!targetModel)
345
- throw new Error(`${modelName} relation error: target model ${targetModelName} not found`);
346
-
347
- let primaryKeyField;
348
-
349
- if (belongsType) {
350
- primaryKeyField = getModelPrimaryKeyField(targetModel);
351
-
352
- if (!primaryKeyField)
353
- throw new Error(`${modelName} relation error: primary key for model ${targetModelName} not found`);
354
-
355
- let pkFieldName = primaryKeyField.field;
356
- if (pkFieldName === 'id')
357
- pkFieldName = 'ID';
358
-
359
- if (!fieldName)
360
- fieldName = `${Nife.camelCaseToSnakeCase(targetModelName)}${Nife.snakeCaseToCamelCase(pkFieldName, true)}`;
361
- } else {
362
- primaryKeyField = getModelPrimaryKeyField(model);
363
-
364
- if (!primaryKeyField)
365
- throw new Error(`${modelName} relation error: primary key for model ${modelName} not found`);
366
-
367
- let pkFieldName = primaryKeyField.field;
368
- if (pkFieldName === 'id')
369
- pkFieldName = 'ID';
370
-
371
- if (!fieldName)
372
- fieldName = `${Nife.camelCaseToSnakeCase(modelName)}${Nife.snakeCaseToCamelCase(pkFieldName, true)}`;
373
- }
374
-
375
- let pkFieldCopy = Nife.extend(Nife.extend.FILTER, (key) => !key.match(/^(field|primaryKey)$/), {}, primaryKeyField);
376
-
377
- // Build relation options for sequelize
378
- let options = Object.assign({}, relation, {
379
- onDelete: relation.onDelete,
380
- onUpdate: relation.onUpdate,
381
- allowNull: (relation.allowNull == null) ? true : relation.allowNull,
382
- foreignKey: (relation.foreignKey) ? relation.foreignKey : Object.assign(pkFieldCopy, { name: fieldName, as: relation.name, field: Nife.camelCaseToSnakeCase(fieldName) }),
383
- });
384
-
385
- // Set relation on model
386
-
387
- // console.log(`Creating model relation (${modelName} -> ${targetModelName}): `, type, options);
388
-
389
- model[type](targetModel, options);
390
- }
391
- }
392
- }
393
-
394
46
  module.exports = {
395
47
  defineModel,
396
- getModelPrimaryKeyField,
397
- buildModelRelations,
398
48
  };