leoric 2.2.3 → 2.3.2

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/History.md CHANGED
@@ -1,3 +1,39 @@
1
+ 2.3.2 / 2022-04-15
2
+ ==================
3
+
4
+ ## What's Changed
5
+ * fix: order by raw with mix-type array in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/298
6
+ * docs: monthly updates and example about egg-orm usage with TypeScript by @cyjake in https://github.com/cyjake/leoric/pull/299
7
+ * docs: monthly updates in en & docmentation about typescript support by @cyjake in https://github.com/cyjake/leoric/pull/300
8
+ * fix: raw query should format replacements with extra blank by @JimmyDaddy in https://github.com/cyjake/leoric/pull/301
9
+ * docs: elaborate on querying by @cyjake in https://github.com/cyjake/leoric/pull/302
10
+ * feat: transaction should return result by @JimmyDaddy in https://github.com/cyjake/leoric/pull/303
11
+
12
+
13
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.3.1...v2.3.2
14
+
15
+ 2.3.1 / 2022-03-22
16
+ ==================
17
+
18
+ ## What's Changed
19
+ * fix: mysql2 Invalid Date compatible by @JimmyDaddy in https://github.com/cyjake/leoric/pull/291
20
+ * fix: order by raw in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/292
21
+ * fix: bulk update query conditions duplicated in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/293
22
+ * fix: bulk destroy query conditions duplicated in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/295
23
+ * fix: drop column if not defined in attributes when alter table by @cyjake in https://github.com/cyjake/leoric/pull/296
24
+
25
+
26
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.3.0...v2.3.1
27
+
28
+ 2.3.0 / 2022-03-10
29
+ ==================
30
+
31
+ ## What's Changed
32
+ * feat: model declaration with decorators by @cyjake in https://github.com/cyjake/leoric/pull/287
33
+ * feat: add VIRTUAL data type by @JimmyDaddy in https://github.com/cyjake/leoric/pull/289
34
+ * fix: create instance dirty check rule fix by @JimmyDaddy in https://github.com/cyjake/leoric/pull/290
35
+
36
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.2.3...v2.3.0
1
37
  2.2.3 / 2022-03-01
2
38
  ==================
3
39
 
package/Readme.md CHANGED
@@ -74,6 +74,32 @@ await realm.sync();
74
74
 
75
75
  A more detailed syntax table may be found at the [documentation](https://leoric.js.org/#syntax-table) site.
76
76
 
77
+ ## TypeScript charged
78
+
79
+ ```ts
80
+ import { Bone, BelongsTo, Column, DataTypes: { TEXT } } from 'leoric';
81
+ import User from './user';
82
+
83
+ export default class Post extends Bone {
84
+ @Column({ autoIncrement: true })
85
+ id: bigint;
86
+
87
+ @Column(TEXT)
88
+ content: string;
89
+
90
+ @Column()
91
+ description: string;
92
+
93
+ @Column()
94
+ userId: bigint;
95
+
96
+ @BelongsTo()
97
+ user: User;
98
+ }
99
+ ```
100
+
101
+ More about TypeScript integration examples can be found at [the TypeScript support documentation](https://leoric.js.org/types)
102
+
77
103
  ## Contributing
78
104
 
79
105
  There are many ways in which you can participate in the project, for example:
@@ -89,6 +115,6 @@ If you are interested in fixing issues and contributing directly to the code bas
89
115
  - Submitting pull requests
90
116
  - Contributing to translations
91
117
 
92
- ## Related Projects
118
+ ## egg-orm
93
119
 
94
- If developing web applications with [egg framework](https://eggjs.org/), it's highly recommended using the [egg-orm](https://github.com/eggjs/egg-orm) plugin.
120
+ If developing web applications with [egg framework](https://eggjs.org/), it's highly recommended using the [egg-orm](https://github.com/eggjs/egg-orm) plugin. More detailed examples about setting up egg-orm with egg framework in either JavaScript or TypeScript can be found at <https://github.com/eggjs/egg-orm/tree/master/examples>
package/index.js CHANGED
@@ -11,6 +11,7 @@ const sequelize = require('./src/adapters/sequelize');
11
11
  const { heresql } = require('./src/utils/string');
12
12
  const Hint = require('./src/hint');
13
13
  const Realm = require('./src/realm');
14
+ const Decorators = require('./src/decorators');
14
15
 
15
16
  /**
16
17
  * @typedef {Object} RawSql
@@ -38,6 +39,7 @@ const connect = async function connect(opts) {
38
39
 
39
40
  Object.assign(Realm.prototype, migrations, { DataTypes });
40
41
  Object.assign(Realm, {
42
+ default: Realm,
41
43
  connect,
42
44
  Bone,
43
45
  Collection,
@@ -47,6 +49,7 @@ Object.assign(Realm, {
47
49
  sequelize,
48
50
  heresql,
49
51
  ...Hint,
52
+ ...Decorators,
50
53
  });
51
54
 
52
55
  module.exports = Realm;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leoric",
3
- "version": "2.2.3",
3
+ "version": "2.3.2",
4
4
  "description": "JavaScript Object-relational mapping alchemy",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -11,7 +11,8 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "jsdoc": "rm -rf docs/api && jsdoc -c .jsdoc.json -d docs/api -t node_modules/@cara/minami",
14
- "pretest": "./test/prepare.sh",
14
+ "prepack": "tsc",
15
+ "pretest": "tsc && ./test/prepare.sh",
15
16
  "test": "./test/start.sh",
16
17
  "test:local": "./test/start.sh",
17
18
  "test:unit": "./test/start.sh unit",
@@ -50,6 +51,7 @@
50
51
  "debug": "^3.1.0",
51
52
  "heredoc": "^1.3.1",
52
53
  "pluralize": "^7.0.0",
54
+ "reflect-metadata": "^0.1.13",
53
55
  "sqlstring": "^2.3.0",
54
56
  "strftime": "^0.10.0",
55
57
  "validator": "^13.5.2"
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { setupSingleHook } = require('../setup_hooks');
4
4
  const { compose, isPlainObject } = require('../utils');
5
+ const Raw = require('../raw');
5
6
 
6
7
  function translateOptions(spell, options) {
7
8
  const { attributes, where, group, order, offset, limit, include, having } = options;
@@ -18,7 +19,7 @@ function translateOptions(spell, options) {
18
19
  if (having) spell.$having(having);
19
20
 
20
21
  if (order) {
21
- if (typeof order === 'string') {
22
+ if (typeof order === 'string' || order instanceof Raw) {
22
23
  spell.$order(order);
23
24
  } else if (Array.isArray(order) && order.length) {
24
25
  if (order.some(item => Array.isArray(item))) {
@@ -34,6 +35,8 @@ function translateOptions(spell, options) {
34
35
  } else if (order.length && order[0]) {
35
36
  // ['created_at', 'asc']
36
37
  spell.$order(order[0], order[1] || '');
38
+ } else if (order instanceof Raw) {
39
+ spell.$order(order);
37
40
  }
38
41
  }
39
42
  }
@@ -317,10 +320,12 @@ module.exports = Bone => {
317
320
  }
318
321
 
319
322
  // proxy to class.destroy({ individualHooks=false }) see https://github.com/sequelize/sequelize/blob/4063c2ab627ad57919d5b45cc7755f077a69fa5e/lib/model.js#L2895 before(after)BulkDestroy
320
- static async bulkDestroy(options = {}) {
323
+ static bulkDestroy(options = {}) {
321
324
  const { where, force } = options;
322
325
  const spell = this._remove(where || {}, force, { ...options });
323
- translateOptions(spell, options);
326
+ const transOptions = { ...options };
327
+ delete transOptions.where;
328
+ translateOptions(spell, transOptions);
324
329
  return spell;
325
330
  }
326
331
 
@@ -496,7 +501,9 @@ module.exports = Bone => {
496
501
  const { where, paranoid = false, validate } = options;
497
502
  const whereConditions = where || {};
498
503
  const spell = super.update(whereConditions, values, { validate, hooks: false, ...options });
499
- translateOptions(spell, options);
504
+ const transOptions = { ...options };
505
+ delete transOptions.where;
506
+ translateOptions(spell, transOptions);
500
507
  if (!paranoid) return spell.unparanoid;
501
508
  return spell;
502
509
  }
package/src/bone.js CHANGED
@@ -7,6 +7,7 @@
7
7
  const util = require('util');
8
8
  const pluralize = require('pluralize');
9
9
  const { executeValidator, LeoricValidateError } = require('./validator');
10
+ require('reflect-metadata');
10
11
 
11
12
  const DataTypes = require('./data_types');
12
13
  const Collection = require('./collection');
@@ -14,8 +15,13 @@ const Spell = require('./spell');
14
15
  const Raw = require('./raw');
15
16
  const { capitalize, camelCase, snakeCase } = require('./utils/string');
16
17
  const { hookNames, setupSingleHook } = require('./setup_hooks');
17
- const { logger } = require('./utils/index');
18
- const { TIMESTAMP_NAMES, LEGACY_TIMESTAMP_COLUMN_MAP } = require('./constants');
18
+ const {
19
+ TIMESTAMP_NAMES,
20
+ LEGACY_TIMESTAMP_COLUMN_MAP,
21
+ ASSOCIATE_METADATA_MAP,
22
+ } = require('./constants');
23
+
24
+ const columnAttributesKey = Symbol('leoric#columns');
19
25
 
20
26
  function looseReadonly(props) {
21
27
  return Object.keys(props).reduce((result, name) => {
@@ -31,10 +37,12 @@ function looseReadonly(props) {
31
37
 
32
38
  function compare(attributes, columnMap) {
33
39
  const diff = {};
40
+ const columnNames = new Set();
34
41
 
35
42
  for (const name in attributes) {
36
43
  const attribute = attributes[name];
37
44
  const { columnName } = attribute;
45
+ columnNames.add(columnName);
38
46
 
39
47
  if (!attribute.equals(columnMap[columnName])) {
40
48
  diff[name] = {
@@ -44,6 +52,12 @@ function compare(attributes, columnMap) {
44
52
  }
45
53
  }
46
54
 
55
+ for (const columnName in columnMap) {
56
+ if (!columnNames.has(columnName)) {
57
+ diff[columnName] = { remove: true };
58
+ }
59
+ }
60
+
47
61
  return diff;
48
62
  }
49
63
 
@@ -156,7 +170,6 @@ class Bone {
156
170
  * Get or set attribute value by name. This method is quite similiar to `jQuery.attr()`. If the attribute isn't selected when queried from database, an error will be thrown when accessing it.
157
171
  *
158
172
  * const post = Post.select('title').first
159
- * post.content // throw Error('Unset attribute "content"')
160
173
  *
161
174
  * This is the underlying method of attribute getter/setters:
162
175
  *
@@ -189,7 +202,6 @@ class Bone {
189
202
  }
190
203
 
191
204
  if (this.#rawUnset.has(name)) {
192
- logger.warn(`unset attribute "${name}"`);
193
205
  return;
194
206
  }
195
207
 
@@ -204,9 +216,9 @@ class Bone {
204
216
  * @memberof Bone
205
217
  */
206
218
  _clone(target) {
207
- this.#raw = Object.assign({}, target.getRaw());
208
- this.#rawSaved = Object.assign({}, target.getRawSaved());
209
- this.#rawPrevious = Object.assign({}, target.getRawPrevious());
219
+ this.#raw = Object.assign({}, this.getRaw(), target.getRaw());
220
+ this.#rawSaved = Object.assign({}, this.getRawSaved(), target.getRawSaved());
221
+ this.#rawPrevious = Object.assign({}, this.getRawPrevious(), target.getRawPrevious());
210
222
  }
211
223
 
212
224
  /**
@@ -235,6 +247,34 @@ class Bone {
235
247
  return attributes.hasOwnProperty(name);
236
248
  }
237
249
 
250
+ /**
251
+ * get attributes except virtuals
252
+ */
253
+ static get columnAttributes() {
254
+ if (this[columnAttributesKey]) return this[columnAttributesKey];
255
+ const { attributes } = this;
256
+ this[columnAttributesKey] = {};
257
+ for (const key in this.attributes) {
258
+ if (!attributes[key].virtual) this[columnAttributesKey][key] = attributes[key];
259
+ }
260
+ return this[columnAttributesKey];
261
+ }
262
+
263
+ /**
264
+ * get actual update/insert columns to avoid empty insert or update
265
+ * @param {Object} data
266
+ * @returns
267
+ */
268
+ static _getColumns(data) {
269
+ if (!Object.keys(data).length) return data;
270
+ const attributes = this.columnAttributes;
271
+ const res = {};
272
+ for (const key in data) {
273
+ if (attributes[key]) res[key] = data[key];
274
+ }
275
+ return res;
276
+ }
277
+
238
278
  getRaw(key) {
239
279
  if (key) return this.#raw[key];
240
280
  return this.#raw;
@@ -320,7 +360,6 @@ class Bone {
320
360
  * post.attributeWas('title') // => 'Leah'
321
361
  */
322
362
  attributeWas(name) {
323
- if (this.#rawUnset.has(name)) throw new Error(`unset attribute "${name}"`);
324
363
  const value = this.#rawSaved[name];
325
364
  return value == null ? null : value;
326
365
  }
@@ -536,18 +575,25 @@ class Bone {
536
575
  * Sync changes made in {@link Bone.raw} back to {@link Bone.rawSaved}.
537
576
  * @private
538
577
  */
539
- syncRaw(changes) {
578
+ syncRaw() {
540
579
  const { attributes } = this.constructor;
541
580
  this.isNewRecord = false;
542
- for (const name of Object.keys(changes || attributes)) {
581
+ for (const name of Object.keys(attributes)) {
543
582
  const attribute = attributes[name];
544
583
  // Take advantage of uncast/cast to create new copy of value
545
- const value = attribute.uncast(this.#raw[name]);
584
+ let value;
585
+ try {
586
+ value = attribute.uncast(this.#raw[name]);
587
+ } catch (error) {
588
+ console.error(error);
589
+ // do not interrupt sync raw
590
+ value = this.#raw[name];
591
+ }
546
592
  if (this.#rawSaved[name] !== undefined) {
547
593
  this.#rawPrevious[name] = this.#rawSaved[name];
548
- } else if (!changes && this.#rawPrevious[name] === undefined) {
594
+ } else if (this.#rawPrevious[name] === undefined && this.#raw[name] != null) {
549
595
  // first persisting
550
- this.#rawPrevious[name] = attribute.cast(value);
596
+ this.#rawPrevious[name] = null;
551
597
  }
552
598
  this.#rawSaved[name] = attribute.cast(value);
553
599
  }
@@ -579,7 +625,10 @@ class Bone {
579
625
  if (this.changed(name)) data[name] = this.attribute(name);
580
626
  }
581
627
 
582
- if (Object.keys(data).length === 0) return Promise.resolve(0);
628
+ if (!Object.keys(Model._getColumns(data)).length) {
629
+ this.syncRaw();
630
+ return Promise.resolve(0);
631
+ }
583
632
 
584
633
  const { createdAt, updatedAt } = Model.timestamps;
585
634
 
@@ -659,7 +708,10 @@ class Bone {
659
708
  }
660
709
  }
661
710
 
662
- if (Object.keys(changes).length === 0) return Promise.resolve(0);
711
+ if (!Object.keys(Model._getColumns(changes)).length) {
712
+ this.syncRaw();
713
+ return Promise.resolve(0);
714
+ }
663
715
  if (this[primaryKey] == null) {
664
716
  throw new Error(`unset primary key ${primaryKey}`);
665
717
  }
@@ -680,7 +732,7 @@ class Bone {
680
732
  for (const key in changes) {
681
733
  this.attribute(key, changes[key]);
682
734
  }
683
- this.syncRaw(changes);
735
+ this.syncRaw();
684
736
  return result.affectedRows;
685
737
  });
686
738
  }
@@ -717,7 +769,6 @@ class Bone {
717
769
  for (const name in attributes) {
718
770
  const value = this.attribute(name);
719
771
  const { defaultValue } = attributes[name];
720
- // console.log(attributes[name], name, defaultValue);
721
772
  if (value != null) {
722
773
  data[name] = value;
723
774
  } else if (value === undefined && defaultValue != null) {
@@ -731,9 +782,15 @@ class Bone {
731
782
  this._validateAttributes(validateValues);
732
783
  }
733
784
 
785
+ if (!Object.keys(Model._getColumns(data)).length) {
786
+ this.syncRaw();
787
+ return this;
788
+ }
789
+
734
790
  const spell = new Spell(Model, opts).$insert(data);
735
791
  return spell.later(result => {
736
792
  this[primaryKey] = result.insertId;
793
+ // this.#rawSaved[primaryKey] = null;
737
794
  this.syncRaw();
738
795
  return this;
739
796
  });
@@ -857,7 +914,10 @@ class Bone {
857
914
  }
858
915
  }
859
916
 
860
- if (Object.keys(data).length === 0) return Promise.resolve(0);
917
+ if (!Object.keys(Model._getColumns(data)).length) {
918
+ this.syncRaw();
919
+ return Promise.resolve(0);
920
+ }
861
921
 
862
922
  const { createdAt, updatedAt } = Model.timestamps;
863
923
 
@@ -978,6 +1038,7 @@ class Bone {
978
1038
  for (const hookName of hookNames) {
979
1039
  if (this[hookName]) setupSingleHook(this, hookName, this[hookName]);
980
1040
  }
1041
+ this[columnAttributesKey] = null;
981
1042
  }
982
1043
 
983
1044
  /**
@@ -990,7 +1051,12 @@ class Bone {
990
1051
  * }
991
1052
  * }
992
1053
  */
993
- static initialize() {}
1054
+ static initialize() {
1055
+ for (const [key, metadataKey] of Object.entries(ASSOCIATE_METADATA_MAP)) {
1056
+ const result = Reflect.getMetadata(metadataKey, this);
1057
+ for (const property in result) this[key].call(this, property, result[property]);
1058
+ }
1059
+ }
994
1060
 
995
1061
  /**
996
1062
  * The primary key of the model, in camelCase.
@@ -1108,6 +1174,7 @@ class Bone {
1108
1174
  Reflect.deleteProperty(this.prototype, originalName);
1109
1175
  this.loadAttribute(newName);
1110
1176
  }
1177
+ this[columnAttributesKey] = null;
1111
1178
  }
1112
1179
 
1113
1180
  /**
@@ -1117,15 +1184,16 @@ class Bone {
1117
1184
  * @param {string} [opts.className]
1118
1185
  * @param {string} [opts.foreignKey]
1119
1186
  */
1120
- static hasOne(name, opts) {
1121
- opts = Object.assign({
1187
+ static hasOne(name, options) {
1188
+ options = ({
1122
1189
  className: capitalize(name),
1123
- foreignKey: this.table + 'Id',
1124
- }, opts);
1190
+ foreignKey: camelCase(`${this.name}Id`),
1191
+ ...options,
1192
+ });
1125
1193
 
1126
- if (opts.through) opts.foreignKey = '';
1194
+ if (options.through) options.foreignKey = '';
1127
1195
 
1128
- this.associate(name, opts);
1196
+ this.associate(name, options);
1129
1197
  }
1130
1198
 
1131
1199
  /**
@@ -1135,16 +1203,17 @@ class Bone {
1135
1203
  * @param {string} [opts.className]
1136
1204
  * @param {string} [opts.foreignKey]
1137
1205
  */
1138
- static hasMany(name, opts) {
1139
- opts = Object.assign({
1206
+ static hasMany(name, options) {
1207
+ options = {
1140
1208
  className: capitalize(pluralize(name, 1)),
1141
- }, opts, {
1209
+ foreignKey: camelCase(`${this.name}Id`),
1210
+ ...options,
1142
1211
  hasMany: true,
1143
- });
1212
+ };
1144
1213
 
1145
- if (opts.through) opts.foreignKey = '';
1214
+ if (options.through) options.foreignKey = '';
1146
1215
 
1147
- this.associate(name, opts);
1216
+ this.associate(name, options);
1148
1217
  }
1149
1218
 
1150
1219
  /**
@@ -1154,15 +1223,15 @@ class Bone {
1154
1223
  * @param {string} [opts.className]
1155
1224
  * @param {string} [opts.foreignKey]
1156
1225
  */
1157
- static belongsTo(name, opts) {
1158
- opts = Object.assign({
1159
- className: capitalize(name),
1160
- }, opts);
1161
-
1162
- let { className, foreignKey } = opts;
1163
- if (!foreignKey) foreignKey = camelCase(className) + 'Id';
1226
+ static belongsTo(name, options = {}) {
1227
+ const { className = capitalize(name) } = options;
1228
+ options = {
1229
+ className,
1230
+ foreignKey: camelCase(`${className}Id`),
1231
+ ...options,
1232
+ };
1164
1233
 
1165
- this.associate(name, Object.assign(opts, { foreignKey, belongsTo: true }));
1234
+ this.associate(name, { ...options, belongsTo: true });
1166
1235
  }
1167
1236
 
1168
1237
  /**
@@ -1182,6 +1251,9 @@ class Bone {
1182
1251
  const { className } = opts;
1183
1252
  const Model = this.models[className];
1184
1253
  if (!Model) throw new Error(`unable to find model "${className}"`);
1254
+ if (opts.foreignKey && Model.attributes[opts.foreignKey] && Model.attributes[opts.foreignKey].virtual) {
1255
+ throw new Error(`unable to use virtual attribute ${opts.foreignKey} as foreign key in model ${Model.name}`);
1256
+ }
1185
1257
 
1186
1258
  const { deletedAt } = this.timestamps;
1187
1259
  if (Model.attributes[deletedAt] && !opts.where) {
@@ -1495,11 +1567,12 @@ class Bone {
1495
1567
 
1496
1568
  static async transaction(callback) {
1497
1569
  const connection = await this.driver.getConnection();
1570
+ let result;
1498
1571
  if (callback.constructor.name === 'AsyncFunction') {
1499
1572
  // if callback is an AsyncFunction
1500
1573
  await this.driver.query('BEGIN', [], { connection, Model: this, command: 'BEGIN' });
1501
1574
  try {
1502
- await callback({ connection });
1575
+ result = await callback({ connection });
1503
1576
  await this.driver.query('COMMIT', [], { connection, Model: this, command: 'COMMIT' });
1504
1577
  } catch (err) {
1505
1578
  await this.driver.query('ROLLBACK', [], { connection, Model: this, command: 'ROLLBACK' });
@@ -1509,7 +1582,6 @@ class Bone {
1509
1582
  }
1510
1583
  } else if (callback.constructor.name === 'GeneratorFunction') {
1511
1584
  const gen = callback({ connection });
1512
- let result;
1513
1585
 
1514
1586
  try {
1515
1587
  await this.driver.query('BEGIN', [], { connection, Model: this, command: 'BEGIN' });
@@ -1529,6 +1601,7 @@ class Bone {
1529
1601
  } else {
1530
1602
  throw new Error('unexpected transaction function, should be GeneratorFunction or AsyncFunction.');
1531
1603
  }
1604
+ return result;
1532
1605
  }
1533
1606
 
1534
1607
  static init(attributes = {}, opts = {}, overrides = {}) {
@@ -1560,6 +1633,7 @@ class Bone {
1560
1633
  return result;
1561
1634
  }, {});
1562
1635
 
1636
+ this[columnAttributesKey] = null;
1563
1637
  Object.defineProperties(this, looseReadonly({ ...hookMethods, attributes, table }));
1564
1638
  }
1565
1639
 
@@ -1579,7 +1653,7 @@ class Bone {
1579
1653
  throw new Error('unable to sync model with custom physic tables');
1580
1654
  }
1581
1655
 
1582
- const { attributes, columns } = this;
1656
+ const { columnAttributes: attributes, columns } = this;
1583
1657
  const columnMap = columns.reduce((result, entry) => {
1584
1658
  result[entry.columnName] = entry;
1585
1659
  return result;
package/src/collection.js CHANGED
@@ -63,14 +63,14 @@ class Collection extends Array {
63
63
  */
64
64
  function instantiatable(spell) {
65
65
  const { columns, groups, Model } = spell;
66
- const { attributes, tableAlias } = Model;
66
+ const { columnAttributes, tableAlias } = Model;
67
67
 
68
68
  if (groups.length > 0) return false;
69
69
  if (columns.length === 0) return true;
70
70
 
71
71
  return columns
72
72
  .filter(({ qualifiers }) => (!qualifiers || qualifiers.includes(tableAlias)))
73
- .every(({ value }) => attributes[value]);
73
+ .every(({ value }) => columnAttributes[value]);
74
74
  }
75
75
 
76
76
  /**
package/src/constants.js CHANGED
@@ -22,9 +22,16 @@ const LEGACY_TIMESTAMP_COLUMN_MAP = {
22
22
 
23
23
  const TIMESTAMP_NAMES = [ 'createdAt', 'updatedAt', 'deletedAt' ];
24
24
 
25
+ const ASSOCIATE_METADATA_MAP = {
26
+ hasMany: Symbol('hasMany'),
27
+ hasOne: Symbol('hasOne'),
28
+ belongsTo: Symbol('belongsTo'),
29
+ };
30
+
25
31
  module.exports = {
26
32
  AGGREGATOR_MAP,
27
33
  LEGACY_TIMESTAMP_MAP,
28
34
  TIMESTAMP_NAMES,
29
- LEGACY_TIMESTAMP_COLUMN_MAP
35
+ LEGACY_TIMESTAMP_COLUMN_MAP,
36
+ ASSOCIATE_METADATA_MAP
30
37
  };