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 +36 -0
- package/Readme.md +28 -2
- package/index.js +3 -0
- package/package.json +4 -2
- package/src/adapters/sequelize.js +11 -4
- package/src/bone.js +116 -42
- 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 +8 -2
- 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/realm.js +1 -1
- package/src/setup_hooks.js +1 -1
- 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,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
|
-
##
|
|
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
|
+
"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
|
-
"
|
|
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
|
|
323
|
+
static bulkDestroy(options = {}) {
|
|
321
324
|
const { where, force } = options;
|
|
322
325
|
const spell = this._remove(where || {}, force, { ...options });
|
|
323
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
|
@@ -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(
|
|
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
|
}
|
|
@@ -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
|
|
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,
|
|
1121
|
-
|
|
1187
|
+
static hasOne(name, options) {
|
|
1188
|
+
options = ({
|
|
1122
1189
|
className: capitalize(name),
|
|
1123
|
-
foreignKey: this.
|
|
1124
|
-
|
|
1190
|
+
foreignKey: camelCase(`${this.name}Id`),
|
|
1191
|
+
...options,
|
|
1192
|
+
});
|
|
1125
1193
|
|
|
1126
|
-
if (
|
|
1194
|
+
if (options.through) options.foreignKey = '';
|
|
1127
1195
|
|
|
1128
|
-
this.associate(name,
|
|
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,
|
|
1139
|
-
|
|
1206
|
+
static hasMany(name, options) {
|
|
1207
|
+
options = {
|
|
1140
1208
|
className: capitalize(pluralize(name, 1)),
|
|
1141
|
-
|
|
1209
|
+
foreignKey: camelCase(`${this.name}Id`),
|
|
1210
|
+
...options,
|
|
1142
1211
|
hasMany: true,
|
|
1143
|
-
}
|
|
1212
|
+
};
|
|
1144
1213
|
|
|
1145
|
-
if (
|
|
1214
|
+
if (options.through) options.foreignKey = '';
|
|
1146
1215
|
|
|
1147
|
-
this.associate(name,
|
|
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,
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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,
|
|
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 {
|
|
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
|
};
|