leoric 2.2.2 → 2.3.1
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 +31 -0
- package/index.js +3 -0
- package/package.json +5 -3
- package/src/adapters/sequelize.js +9 -4
- package/src/bone.js +114 -40
- package/src/collection.js +2 -2
- package/src/constants.js +8 -1
- package/src/data_types.js +65 -12
- package/src/decorators.js +77 -0
- package/src/decorators.js.map +1 -0
- package/src/decorators.ts +96 -0
- package/src/drivers/abstract/attribute.js +17 -3
- package/src/drivers/abstract/schema.js +5 -2
- package/src/drivers/abstract/spellbook.js +15 -15
- package/src/drivers/mysql/attribute.js +11 -2
- package/src/drivers/mysql/index.js +1 -1
- package/src/drivers/mysql/schema.js +1 -1
- package/src/drivers/mysql/spellbook.js +4 -4
- package/src/drivers/postgres/index.js +1 -1
- package/src/drivers/postgres/schema.js +7 -1
- package/src/drivers/sqlite/index.js +1 -1
- package/src/drivers/sqlite/schema.js +10 -2
- package/src/drivers/sqlite/spellbook.js +1 -1
- package/src/expr_formatter.js +3 -0
- package/src/setup_hooks.js +21 -2
- package/src/spell.js +63 -29
- package/types/data_types.d.ts +15 -1
- package/types/index.d.ts +13 -7
package/History.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
2.3.1 / 2022-03-22
|
|
2
|
+
==================
|
|
3
|
+
|
|
4
|
+
## What's Changed
|
|
5
|
+
* fix: mysql2 Invalid Date compatible by @JimmyDaddy in https://github.com/cyjake/leoric/pull/291
|
|
6
|
+
* fix: order by raw in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/292
|
|
7
|
+
* fix: bulk update query conditions duplicated in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/293
|
|
8
|
+
* fix: bulk destroy query conditions duplicated in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/295
|
|
9
|
+
* fix: drop column if not defined in attributes when alter table by @cyjake in https://github.com/cyjake/leoric/pull/296
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v2.3.0...v2.3.1
|
|
13
|
+
|
|
14
|
+
2.3.0 / 2022-03-10
|
|
15
|
+
==================
|
|
16
|
+
|
|
17
|
+
## What's Changed
|
|
18
|
+
* feat: model declaration with decorators by @cyjake in https://github.com/cyjake/leoric/pull/287
|
|
19
|
+
* feat: add VIRTUAL data type by @JimmyDaddy in https://github.com/cyjake/leoric/pull/289
|
|
20
|
+
* fix: create instance dirty check rule fix by @JimmyDaddy in https://github.com/cyjake/leoric/pull/290
|
|
21
|
+
|
|
22
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v2.2.3...v2.3.0
|
|
23
|
+
2.2.3 / 2022-03-01
|
|
24
|
+
==================
|
|
25
|
+
|
|
26
|
+
## What's Changed
|
|
27
|
+
* fix: normalize attribute defaultValue by @cyjake in https://github.com/cyjake/leoric/pull/285
|
|
28
|
+
* fix: instance beforeUpdate hooks should not modify any Raw if there are no Raw assignment in them by @JimmyDaddy in https://github.com/cyjake/leoric/pull/283
|
|
29
|
+
|
|
30
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v2.2.2...v2.2.3
|
|
31
|
+
|
|
1
32
|
2.2.2 / 2022-02-28
|
|
2
33
|
==================
|
|
3
34
|
|
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.
|
|
3
|
+
"version": "2.3.1",
|
|
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
|
-
"
|
|
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"
|
|
@@ -72,6 +74,6 @@
|
|
|
72
74
|
"pg": "^8.5.1",
|
|
73
75
|
"sinon": "^10.0.0",
|
|
74
76
|
"sqlite3": "^5.0.2",
|
|
75
|
-
"typescript": "^4.
|
|
77
|
+
"typescript": "^4.6.2"
|
|
76
78
|
}
|
|
77
79
|
}
|
|
@@ -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))) {
|
|
@@ -317,10 +318,12 @@ module.exports = Bone => {
|
|
|
317
318
|
}
|
|
318
319
|
|
|
319
320
|
// proxy to class.destroy({ individualHooks=false }) see https://github.com/sequelize/sequelize/blob/4063c2ab627ad57919d5b45cc7755f077a69fa5e/lib/model.js#L2895 before(after)BulkDestroy
|
|
320
|
-
static
|
|
321
|
+
static bulkDestroy(options = {}) {
|
|
321
322
|
const { where, force } = options;
|
|
322
323
|
const spell = this._remove(where || {}, force, { ...options });
|
|
323
|
-
|
|
324
|
+
const transOptions = { ...options };
|
|
325
|
+
delete transOptions.where;
|
|
326
|
+
translateOptions(spell, transOptions);
|
|
324
327
|
return spell;
|
|
325
328
|
}
|
|
326
329
|
|
|
@@ -496,7 +499,9 @@ module.exports = Bone => {
|
|
|
496
499
|
const { where, paranoid = false, validate } = options;
|
|
497
500
|
const whereConditions = where || {};
|
|
498
501
|
const spell = super.update(whereConditions, values, { validate, hooks: false, ...options });
|
|
499
|
-
|
|
502
|
+
const transOptions = { ...options };
|
|
503
|
+
delete transOptions.where;
|
|
504
|
+
translateOptions(spell, transOptions);
|
|
500
505
|
if (!paranoid) return spell.unparanoid;
|
|
501
506
|
return spell;
|
|
502
507
|
}
|
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 {
|
|
18
|
-
|
|
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
|
|
|
@@ -199,14 +211,14 @@ class Bone {
|
|
|
199
211
|
}
|
|
200
212
|
|
|
201
213
|
/**
|
|
202
|
-
*
|
|
203
|
-
* clone instance
|
|
214
|
+
* @protected clone instance
|
|
204
215
|
* @param {Bone} target
|
|
205
216
|
* @memberof Bone
|
|
206
217
|
*/
|
|
207
218
|
_clone(target) {
|
|
208
|
-
Object.assign(this
|
|
209
|
-
Object.assign(this
|
|
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(
|
|
578
|
+
syncRaw() {
|
|
540
579
|
const { attributes } = this.constructor;
|
|
541
580
|
this.isNewRecord = false;
|
|
542
|
-
for (const name of Object.keys(
|
|
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
|
-
|
|
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 (
|
|
594
|
+
} else if (this.#rawPrevious[name] === undefined && this.#raw[name] != null) {
|
|
549
595
|
// first persisting
|
|
550
|
-
this.#rawPrevious[name] =
|
|
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
|
|
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
|
|
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(
|
|
735
|
+
this.syncRaw();
|
|
684
736
|
return result.affectedRows;
|
|
685
737
|
});
|
|
686
738
|
}
|
|
@@ -730,9 +782,15 @@ class Bone {
|
|
|
730
782
|
this._validateAttributes(validateValues);
|
|
731
783
|
}
|
|
732
784
|
|
|
785
|
+
if (!Object.keys(Model._getColumns(data)).length) {
|
|
786
|
+
this.syncRaw();
|
|
787
|
+
return this;
|
|
788
|
+
}
|
|
789
|
+
|
|
733
790
|
const spell = new Spell(Model, opts).$insert(data);
|
|
734
791
|
return spell.later(result => {
|
|
735
792
|
this[primaryKey] = result.insertId;
|
|
793
|
+
// this.#rawSaved[primaryKey] = null;
|
|
736
794
|
this.syncRaw();
|
|
737
795
|
return this;
|
|
738
796
|
});
|
|
@@ -856,7 +914,10 @@ class Bone {
|
|
|
856
914
|
}
|
|
857
915
|
}
|
|
858
916
|
|
|
859
|
-
if (Object.keys(data).length
|
|
917
|
+
if (!Object.keys(Model._getColumns(data)).length) {
|
|
918
|
+
this.syncRaw();
|
|
919
|
+
return Promise.resolve(0);
|
|
920
|
+
}
|
|
860
921
|
|
|
861
922
|
const { createdAt, updatedAt } = Model.timestamps;
|
|
862
923
|
|
|
@@ -977,6 +1038,7 @@ class Bone {
|
|
|
977
1038
|
for (const hookName of hookNames) {
|
|
978
1039
|
if (this[hookName]) setupSingleHook(this, hookName, this[hookName]);
|
|
979
1040
|
}
|
|
1041
|
+
this[columnAttributesKey] = null;
|
|
980
1042
|
}
|
|
981
1043
|
|
|
982
1044
|
/**
|
|
@@ -989,7 +1051,12 @@ class Bone {
|
|
|
989
1051
|
* }
|
|
990
1052
|
* }
|
|
991
1053
|
*/
|
|
992
|
-
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
|
+
}
|
|
993
1060
|
|
|
994
1061
|
/**
|
|
995
1062
|
* The primary key of the model, in camelCase.
|
|
@@ -1107,6 +1174,7 @@ class Bone {
|
|
|
1107
1174
|
Reflect.deleteProperty(this.prototype, originalName);
|
|
1108
1175
|
this.loadAttribute(newName);
|
|
1109
1176
|
}
|
|
1177
|
+
this[columnAttributesKey] = null;
|
|
1110
1178
|
}
|
|
1111
1179
|
|
|
1112
1180
|
/**
|
|
@@ -1116,15 +1184,16 @@ class Bone {
|
|
|
1116
1184
|
* @param {string} [opts.className]
|
|
1117
1185
|
* @param {string} [opts.foreignKey]
|
|
1118
1186
|
*/
|
|
1119
|
-
static hasOne(name,
|
|
1120
|
-
|
|
1187
|
+
static hasOne(name, options) {
|
|
1188
|
+
options = ({
|
|
1121
1189
|
className: capitalize(name),
|
|
1122
|
-
foreignKey: this.
|
|
1123
|
-
|
|
1190
|
+
foreignKey: camelCase(`${this.name}Id`),
|
|
1191
|
+
...options,
|
|
1192
|
+
});
|
|
1124
1193
|
|
|
1125
|
-
if (
|
|
1194
|
+
if (options.through) options.foreignKey = '';
|
|
1126
1195
|
|
|
1127
|
-
this.associate(name,
|
|
1196
|
+
this.associate(name, options);
|
|
1128
1197
|
}
|
|
1129
1198
|
|
|
1130
1199
|
/**
|
|
@@ -1134,16 +1203,17 @@ class Bone {
|
|
|
1134
1203
|
* @param {string} [opts.className]
|
|
1135
1204
|
* @param {string} [opts.foreignKey]
|
|
1136
1205
|
*/
|
|
1137
|
-
static hasMany(name,
|
|
1138
|
-
|
|
1206
|
+
static hasMany(name, options) {
|
|
1207
|
+
options = {
|
|
1139
1208
|
className: capitalize(pluralize(name, 1)),
|
|
1140
|
-
|
|
1209
|
+
foreignKey: camelCase(`${this.name}Id`),
|
|
1210
|
+
...options,
|
|
1141
1211
|
hasMany: true,
|
|
1142
|
-
}
|
|
1212
|
+
};
|
|
1143
1213
|
|
|
1144
|
-
if (
|
|
1214
|
+
if (options.through) options.foreignKey = '';
|
|
1145
1215
|
|
|
1146
|
-
this.associate(name,
|
|
1216
|
+
this.associate(name, options);
|
|
1147
1217
|
}
|
|
1148
1218
|
|
|
1149
1219
|
/**
|
|
@@ -1153,15 +1223,15 @@ class Bone {
|
|
|
1153
1223
|
* @param {string} [opts.className]
|
|
1154
1224
|
* @param {string} [opts.foreignKey]
|
|
1155
1225
|
*/
|
|
1156
|
-
static belongsTo(name,
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1226
|
+
static belongsTo(name, options = {}) {
|
|
1227
|
+
const { className = capitalize(name) } = options;
|
|
1228
|
+
options = {
|
|
1229
|
+
className,
|
|
1230
|
+
foreignKey: camelCase(`${className}Id`),
|
|
1231
|
+
...options,
|
|
1232
|
+
};
|
|
1163
1233
|
|
|
1164
|
-
this.associate(name,
|
|
1234
|
+
this.associate(name, { ...options, belongsTo: true });
|
|
1165
1235
|
}
|
|
1166
1236
|
|
|
1167
1237
|
/**
|
|
@@ -1181,6 +1251,9 @@ class Bone {
|
|
|
1181
1251
|
const { className } = opts;
|
|
1182
1252
|
const Model = this.models[className];
|
|
1183
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
|
+
}
|
|
1184
1257
|
|
|
1185
1258
|
const { deletedAt } = this.timestamps;
|
|
1186
1259
|
if (Model.attributes[deletedAt] && !opts.where) {
|
|
@@ -1559,6 +1632,7 @@ class Bone {
|
|
|
1559
1632
|
return result;
|
|
1560
1633
|
}, {});
|
|
1561
1634
|
|
|
1635
|
+
this[columnAttributesKey] = null;
|
|
1562
1636
|
Object.defineProperties(this, looseReadonly({ ...hookMethods, attributes, table }));
|
|
1563
1637
|
}
|
|
1564
1638
|
|
|
@@ -1578,7 +1652,7 @@ class Bone {
|
|
|
1578
1652
|
throw new Error('unable to sync model with custom physic tables');
|
|
1579
1653
|
}
|
|
1580
1654
|
|
|
1581
|
-
const { attributes, columns } = this;
|
|
1655
|
+
const { columnAttributes: attributes, columns } = this;
|
|
1582
1656
|
const columnMap = columns.reduce((result, entry) => {
|
|
1583
1657
|
result[entry.columnName] = entry;
|
|
1584
1658
|
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 {
|
|
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 }) =>
|
|
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
|
};
|
package/src/data_types.js
CHANGED
|
@@ -17,17 +17,20 @@ class DataType {
|
|
|
17
17
|
const {
|
|
18
18
|
STRING, TEXT,
|
|
19
19
|
DATE, DATEONLY,
|
|
20
|
-
TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT,
|
|
20
|
+
TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT, DECIMAL,
|
|
21
21
|
BOOLEAN,
|
|
22
22
|
BINARY, VARBINARY, BLOB,
|
|
23
23
|
} = this;
|
|
24
|
-
const [ , dataType,
|
|
25
|
-
const
|
|
24
|
+
const [ , dataType, ...matches ] = columnType.match(/(\w+)(?:\((\d+)(?:,(\d+))?\))?/);
|
|
25
|
+
const params = [];
|
|
26
|
+
for (let i = 0; i < matches.length; i++) {
|
|
27
|
+
if (matches[i] != null) params[i] = parseInt(matches[i], 10);
|
|
28
|
+
}
|
|
26
29
|
|
|
27
30
|
switch (dataType) {
|
|
28
31
|
case 'varchar':
|
|
29
32
|
case 'char':
|
|
30
|
-
return new STRING(
|
|
33
|
+
return new STRING(...params);
|
|
31
34
|
// longtext is only for MySQL
|
|
32
35
|
case 'longtext':
|
|
33
36
|
return new TEXT('long');
|
|
@@ -40,30 +43,31 @@ class DataType {
|
|
|
40
43
|
case 'datetime':
|
|
41
44
|
case 'timestamp':
|
|
42
45
|
// new DATE(precision)
|
|
43
|
-
return new DATE(
|
|
46
|
+
return new DATE(...params);
|
|
44
47
|
case 'decimal':
|
|
48
|
+
return new DECIMAL(...params);
|
|
45
49
|
case 'int':
|
|
46
50
|
case 'integer':
|
|
47
51
|
case 'numeric':
|
|
48
|
-
return new INTEGER(
|
|
52
|
+
return new INTEGER(...params);
|
|
49
53
|
case 'mediumint':
|
|
50
|
-
return new MEDIUMINT(
|
|
54
|
+
return new MEDIUMINT(...params);
|
|
51
55
|
case 'smallint':
|
|
52
|
-
return new SMALLINT(
|
|
56
|
+
return new SMALLINT(...params);
|
|
53
57
|
case 'tinyint':
|
|
54
|
-
return new TINYINT(
|
|
58
|
+
return new TINYINT(...params);
|
|
55
59
|
case 'bigint':
|
|
56
|
-
return new BIGINT(
|
|
60
|
+
return new BIGINT(...params);
|
|
57
61
|
case 'boolean':
|
|
58
62
|
return new BOOLEAN();
|
|
59
63
|
// mysql only
|
|
60
64
|
case 'binary':
|
|
61
65
|
// postgres only
|
|
62
66
|
case 'bytea':
|
|
63
|
-
return new BINARY(
|
|
67
|
+
return new BINARY(...params);
|
|
64
68
|
// mysql only
|
|
65
69
|
case 'varbinary':
|
|
66
|
-
return new VARBINARY(
|
|
70
|
+
return new VARBINARY(...params);
|
|
67
71
|
case 'longblob':
|
|
68
72
|
return new BLOB('long');
|
|
69
73
|
case 'mediumblob':
|
|
@@ -273,6 +277,41 @@ class BIGINT extends INTEGER {
|
|
|
273
277
|
}
|
|
274
278
|
}
|
|
275
279
|
|
|
280
|
+
/**
|
|
281
|
+
* fixed-point decimal types
|
|
282
|
+
* @example
|
|
283
|
+
* DECIMAL
|
|
284
|
+
* DECIMAL.UNSIGNED
|
|
285
|
+
* DECIMAL(5, 2)
|
|
286
|
+
* @param {number} precision
|
|
287
|
+
* @param {number} scale
|
|
288
|
+
* - https://dev.mysql.com/doc/refman/8.0/en/fixed-point-types.html
|
|
289
|
+
*/
|
|
290
|
+
class DECIMAL extends INTEGER {
|
|
291
|
+
constructor(precision, scale) {
|
|
292
|
+
super();
|
|
293
|
+
this.dataType = 'decimal';
|
|
294
|
+
this.precision = precision;
|
|
295
|
+
this.scale = scale;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
toSqlString() {
|
|
299
|
+
const { precision, scale, unsigned, zerofill } = this;
|
|
300
|
+
const dataType = this.dataType.toUpperCase();
|
|
301
|
+
const chunks = [];
|
|
302
|
+
if (precision > 0 && scale >= 0) {
|
|
303
|
+
chunks.push(`${dataType}(${precision},${scale})`);
|
|
304
|
+
} else if (precision > 0) {
|
|
305
|
+
chunks.push(`${dataType}(${precision})`);
|
|
306
|
+
} else {
|
|
307
|
+
chunks.push(dataType);
|
|
308
|
+
}
|
|
309
|
+
if (unsigned) chunks.push('UNSIGNED');
|
|
310
|
+
if (zerofill) chunks.push('ZEROFILL');
|
|
311
|
+
return chunks.join(' ');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
276
315
|
const rDateFormat = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:[,.]\d{3,6})$/;
|
|
277
316
|
|
|
278
317
|
class DATE extends DataType {
|
|
@@ -475,6 +514,18 @@ class JSONB extends JSON {
|
|
|
475
514
|
}
|
|
476
515
|
}
|
|
477
516
|
|
|
517
|
+
class VIRTUAL extends DataType {
|
|
518
|
+
constructor() {
|
|
519
|
+
super();
|
|
520
|
+
this.dataType = 'virtual';
|
|
521
|
+
this.virtual = true;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
toSqlString() {
|
|
525
|
+
return 'VIRTUAL';
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
478
529
|
const DataTypes = {
|
|
479
530
|
STRING,
|
|
480
531
|
TINYINT,
|
|
@@ -482,6 +533,7 @@ const DataTypes = {
|
|
|
482
533
|
MEDIUMINT,
|
|
483
534
|
INTEGER,
|
|
484
535
|
BIGINT,
|
|
536
|
+
DECIMAL,
|
|
485
537
|
DATE,
|
|
486
538
|
DATEONLY,
|
|
487
539
|
BOOLEAN,
|
|
@@ -491,6 +543,7 @@ const DataTypes = {
|
|
|
491
543
|
JSONB,
|
|
492
544
|
BINARY,
|
|
493
545
|
VARBINARY,
|
|
546
|
+
VIRTUAL,
|
|
494
547
|
};
|
|
495
548
|
|
|
496
549
|
Object.assign(DataType, DataTypes);
|