@nocobase/database 0.9.3-alpha.1 → 0.9.4-alpha.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/lib/collection.d.ts +9 -9
- package/lib/collection.js +100 -96
- package/lib/database.d.ts +4 -4
- package/lib/database.js +25 -53
- package/lib/eager-loading/eager-loading-tree.d.ts +23 -0
- package/lib/eager-loading/eager-loading-tree.js +338 -0
- package/lib/filter-parser.d.ts +1 -7
- package/lib/filter-parser.js +27 -7
- package/lib/listeners/append-child-collection-name-after-repository-find.d.ts +5 -0
- package/lib/listeners/append-child-collection-name-after-repository-find.js +40 -0
- package/lib/listeners/index.js +2 -0
- package/lib/mock-database.js +3 -1
- package/lib/operators/string.js +1 -1
- package/lib/options-parser.js +4 -0
- package/lib/query-interface/postgres-query-interface.js +2 -2
- package/lib/relation-repository/belongs-to-many-repository.d.ts +2 -1
- package/lib/relation-repository/belongs-to-many-repository.js +58 -37
- package/lib/relation-repository/hasmany-repository.d.ts +2 -1
- package/lib/relation-repository/hasmany-repository.js +31 -16
- package/lib/relation-repository/multiple-relation-repository.js +8 -26
- package/lib/relation-repository/relation-repository.d.ts +1 -7
- package/lib/relation-repository/single-relation-repository.d.ts +1 -1
- package/lib/relation-repository/single-relation-repository.js +10 -16
- package/lib/repository.d.ts +11 -8
- package/lib/repository.js +104 -89
- package/lib/sql-parser/postgres.js +41 -0
- package/lib/update-guard.d.ts +1 -1
- package/lib/update-guard.js +16 -13
- package/lib/utils.d.ts +0 -7
- package/lib/utils.js +0 -76
- package/package.json +4 -4
- package/src/__tests__/eager-loading/eager-loading-tree.test.ts +393 -0
- package/src/__tests__/migrator.test.ts +4 -0
- package/src/__tests__/relation-repository/hasone-repository.test.ts +1 -0
- package/src/__tests__/repository/aggregation.test.ts +297 -0
- package/src/__tests__/repository/count.test.ts +1 -1
- package/src/__tests__/repository/find.test.ts +10 -1
- package/src/__tests__/repository.test.ts +30 -0
- package/src/__tests__/update-guard.test.ts +13 -0
- package/src/collection.ts +74 -66
- package/src/database.ts +26 -42
- package/src/eager-loading/eager-loading-tree.ts +304 -0
- package/src/filter-parser.ts +16 -2
- package/src/listeners/adjacency-list.ts +1 -3
- package/src/listeners/append-child-collection-name-after-repository-find.ts +31 -0
- package/src/listeners/index.ts +2 -0
- package/src/mock-database.ts +3 -1
- package/src/operators/notIn.ts +1 -0
- package/src/operators/string.ts +1 -1
- package/src/options-parser.ts +5 -0
- package/src/query-interface/postgres-query-interface.ts +1 -1
- package/src/relation-repository/belongs-to-many-repository.ts +33 -1
- package/src/relation-repository/hasmany-repository.ts +17 -0
- package/src/relation-repository/multiple-relation-repository.ts +14 -19
- package/src/relation-repository/single-relation-repository.ts +13 -15
- package/src/repository.ts +79 -36
- package/src/sql-parser/postgres.js +25505 -0
- package/src/update-guard.ts +21 -16
- package/src/utils.ts +0 -61
package/lib/update-guard.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class UpdateGuard {
|
|
|
12
12
|
private associationKeysToBeUpdate;
|
|
13
13
|
private blackList;
|
|
14
14
|
private whiteList;
|
|
15
|
+
static fromOptions(model: any, options: any): UpdateGuard;
|
|
15
16
|
setAction(action: UpdateAction): void;
|
|
16
17
|
setModel(model: ModelStatic<any>): void;
|
|
17
18
|
setAssociationKeysToBeUpdate(associationKeysToBeUpdate: AssociationKeysToBeUpdate): void;
|
|
@@ -22,6 +23,5 @@ export declare class UpdateGuard {
|
|
|
22
23
|
* @param values
|
|
23
24
|
*/
|
|
24
25
|
sanitize(values: UpdateValues): {};
|
|
25
|
-
static fromOptions(model: any, options: any): UpdateGuard;
|
|
26
26
|
}
|
|
27
27
|
export {};
|
package/lib/update-guard.js
CHANGED
|
@@ -22,6 +22,18 @@ class UpdateGuard {
|
|
|
22
22
|
this.blackList = void 0;
|
|
23
23
|
this.whiteList = void 0;
|
|
24
24
|
}
|
|
25
|
+
static fromOptions(model, options) {
|
|
26
|
+
const guard = new UpdateGuard();
|
|
27
|
+
guard.setModel(model);
|
|
28
|
+
guard.setWhiteList(options.whitelist);
|
|
29
|
+
guard.setBlackList(options.blacklist);
|
|
30
|
+
guard.setAction(_lodash().default.get(options, 'action', 'update'));
|
|
31
|
+
guard.setAssociationKeysToBeUpdate(options.updateAssociationValues);
|
|
32
|
+
if (options.underscored) {
|
|
33
|
+
guard.underscored = options.underscored;
|
|
34
|
+
}
|
|
35
|
+
return guard;
|
|
36
|
+
}
|
|
25
37
|
setAction(action) {
|
|
26
38
|
this.action = action;
|
|
27
39
|
}
|
|
@@ -67,6 +79,9 @@ class UpdateGuard {
|
|
|
67
79
|
Object.keys(associationsValues).forEach(association => {
|
|
68
80
|
let associationValues = associationsValues[association];
|
|
69
81
|
const filterAssociationToBeUpdate = value => {
|
|
82
|
+
if (value === null) {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
70
85
|
const associationKeysToBeUpdate = this.associationKeysToBeUpdate || [];
|
|
71
86
|
if (associationKeysToBeUpdate.includes(association)) {
|
|
72
87
|
return value;
|
|
@@ -88,7 +103,7 @@ class UpdateGuard {
|
|
|
88
103
|
};
|
|
89
104
|
if (Array.isArray(associationValues)) {
|
|
90
105
|
associationValues = associationValues.map(value => {
|
|
91
|
-
if (typeof value == 'string' || typeof value == 'number') {
|
|
106
|
+
if (value === undefined || value === null || typeof value == 'string' || typeof value == 'number') {
|
|
92
107
|
return value;
|
|
93
108
|
} else {
|
|
94
109
|
return sanitizeValue(value);
|
|
@@ -123,17 +138,5 @@ class UpdateGuard {
|
|
|
123
138
|
}, {});
|
|
124
139
|
return result;
|
|
125
140
|
}
|
|
126
|
-
static fromOptions(model, options) {
|
|
127
|
-
const guard = new UpdateGuard();
|
|
128
|
-
guard.setModel(model);
|
|
129
|
-
guard.setWhiteList(options.whitelist);
|
|
130
|
-
guard.setBlackList(options.blacklist);
|
|
131
|
-
guard.setAction(_lodash().default.get(options, 'action', 'update'));
|
|
132
|
-
guard.setAssociationKeysToBeUpdate(options.updateAssociationValues);
|
|
133
|
-
if (options.underscored) {
|
|
134
|
-
guard.underscored = options.underscored;
|
|
135
|
-
}
|
|
136
|
-
return guard;
|
|
137
|
-
}
|
|
138
141
|
}
|
|
139
142
|
exports.UpdateGuard = UpdateGuard;
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import Database from './database';
|
|
2
|
-
import { Model } from './model';
|
|
3
|
-
declare type HandleAppendsQueryOptions = {
|
|
4
|
-
templateModel: any;
|
|
5
|
-
queryPromises: Array<any>;
|
|
6
|
-
};
|
|
7
|
-
export declare function handleAppendsQuery(options: HandleAppendsQueryOptions): Promise<Model<any, any>[]>;
|
|
8
2
|
export declare function md5(value: string): string;
|
|
9
3
|
export declare function checkIdentifier(value: string): void;
|
|
10
4
|
export declare function getTableName(collectionName: string, options: any): any;
|
|
11
5
|
export declare function snakeCase(name: string): any;
|
|
12
6
|
export declare function patchSequelizeQueryInterface(db: Database): void;
|
|
13
7
|
export declare function percent2float(value: string): number;
|
|
14
|
-
export {};
|
package/lib/utils.js
CHANGED
|
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.checkIdentifier = checkIdentifier;
|
|
7
7
|
exports.getTableName = getTableName;
|
|
8
|
-
exports.handleAppendsQuery = handleAppendsQuery;
|
|
9
8
|
exports.md5 = md5;
|
|
10
9
|
exports.patchSequelizeQueryInterface = patchSequelizeQueryInterface;
|
|
11
10
|
exports.percent2float = percent2float;
|
|
@@ -26,81 +25,6 @@ function _lodash() {
|
|
|
26
25
|
return data;
|
|
27
26
|
}
|
|
28
27
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
30
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
31
|
-
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
32
|
-
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
33
|
-
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
34
|
-
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
35
|
-
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
36
|
-
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
37
|
-
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
|
38
|
-
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
39
|
-
function handleAppendsQuery(_x) {
|
|
40
|
-
return _handleAppendsQuery.apply(this, arguments);
|
|
41
|
-
}
|
|
42
|
-
function _handleAppendsQuery() {
|
|
43
|
-
_handleAppendsQuery = _asyncToGenerator(function* (options) {
|
|
44
|
-
const templateModel = options.templateModel,
|
|
45
|
-
queryPromises = options.queryPromises;
|
|
46
|
-
if (!templateModel) {
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
const primaryKey = templateModel.constructor.primaryKeyAttribute;
|
|
50
|
-
const results = yield Promise.all(queryPromises);
|
|
51
|
-
let rows;
|
|
52
|
-
var _iterator = _createForOfIteratorHelper(results),
|
|
53
|
-
_step;
|
|
54
|
-
try {
|
|
55
|
-
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
56
|
-
const appendedResult = _step.value;
|
|
57
|
-
if (!rows) {
|
|
58
|
-
rows = appendedResult.rows;
|
|
59
|
-
if (rows.length == 0) {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
const modelOptions = templateModel['_options'];
|
|
63
|
-
var _iterator2 = _createForOfIteratorHelper(rows),
|
|
64
|
-
_step2;
|
|
65
|
-
try {
|
|
66
|
-
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
67
|
-
const row = _step2.value;
|
|
68
|
-
row['_options'] = _objectSpread(_objectSpread({}, row['_options']), {}, {
|
|
69
|
-
include: modelOptions['include'],
|
|
70
|
-
includeNames: modelOptions['includeNames'],
|
|
71
|
-
includeMap: modelOptions['includeMap']
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
} catch (err) {
|
|
75
|
-
_iterator2.e(err);
|
|
76
|
-
} finally {
|
|
77
|
-
_iterator2.f();
|
|
78
|
-
}
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
for (let i = 0; i < appendedResult.rows.length; i++) {
|
|
82
|
-
const appendingRow = appendedResult.rows[i];
|
|
83
|
-
const key = appendedResult.include.association;
|
|
84
|
-
const val = appendingRow.get(key);
|
|
85
|
-
const rowKey = appendingRow.get(primaryKey);
|
|
86
|
-
const targetIndex = rows.findIndex(row => row.get(primaryKey) === rowKey);
|
|
87
|
-
if (targetIndex === -1) {
|
|
88
|
-
throw new Error('target row not found');
|
|
89
|
-
}
|
|
90
|
-
rows[targetIndex].set(key, val, {
|
|
91
|
-
raw: true
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
} catch (err) {
|
|
96
|
-
_iterator.e(err);
|
|
97
|
-
} finally {
|
|
98
|
-
_iterator.f();
|
|
99
|
-
}
|
|
100
|
-
return rows;
|
|
101
|
-
});
|
|
102
|
-
return _handleAppendsQuery.apply(this, arguments);
|
|
103
|
-
}
|
|
104
28
|
function md5(value) {
|
|
105
29
|
return _crypto().default.createHash('md5').update(value).digest('hex');
|
|
106
30
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/database",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4-alpha.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@nocobase/logger": "0.9.
|
|
10
|
-
"@nocobase/utils": "0.9.
|
|
9
|
+
"@nocobase/logger": "0.9.4-alpha.1",
|
|
10
|
+
"@nocobase/utils": "0.9.4-alpha.1",
|
|
11
11
|
"async-mutex": "^0.3.2",
|
|
12
12
|
"cron-parser": "4.4.0",
|
|
13
13
|
"deepmerge": "^4.2.2",
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"url": "git+https://github.com/nocobase/nocobase.git",
|
|
29
29
|
"directory": "packages/database"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "0b4936be557be918dbdf8196dadcbc7eb395906d"
|
|
32
32
|
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import Database, { mockDatabase } from '@nocobase/database';
|
|
2
|
+
import { EagerLoadingTree } from '../../eager-loading/eager-loading-tree';
|
|
3
|
+
|
|
4
|
+
describe('Eager loading tree', () => {
|
|
5
|
+
let db: Database;
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
db = mockDatabase({
|
|
8
|
+
tablePrefix: '',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
await db.clean({ drop: true });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await db.close();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should handle fields filter', async () => {
|
|
19
|
+
const User = db.collection({
|
|
20
|
+
name: 'users',
|
|
21
|
+
fields: [
|
|
22
|
+
{ type: 'string', name: 'name' },
|
|
23
|
+
{ type: 'hasOne', name: 'profile' },
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const Profile = db.collection({
|
|
28
|
+
name: 'profiles',
|
|
29
|
+
fields: [
|
|
30
|
+
{ type: 'integer', name: 'age' },
|
|
31
|
+
{ type: 'string', name: 'address' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await db.sync();
|
|
36
|
+
|
|
37
|
+
const users = await User.repository.create({
|
|
38
|
+
values: [
|
|
39
|
+
{
|
|
40
|
+
name: 'u1',
|
|
41
|
+
profile: { age: 1, address: 'u1 address' },
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'u2',
|
|
45
|
+
profile: { age: 2, address: 'u2 address' },
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const findOptions = User.repository.buildQueryOptions({
|
|
51
|
+
fields: ['profile', 'profile.age', 'name'],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
|
|
55
|
+
model: User.model,
|
|
56
|
+
rootAttributes: findOptions.attributes,
|
|
57
|
+
includeOption: findOptions.include,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await eagerLoadingTree.load(users.map((item) => item.id));
|
|
61
|
+
const root = eagerLoadingTree.root;
|
|
62
|
+
|
|
63
|
+
const u1 = root.instances.find((item) => item.get('name') === 'u1');
|
|
64
|
+
const data = u1.toJSON();
|
|
65
|
+
expect(data['id']).not.toBeDefined();
|
|
66
|
+
expect(data['name']).toBeDefined();
|
|
67
|
+
expect(data['profile']).toBeDefined();
|
|
68
|
+
expect(data['profile']['age']).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should load has many', async () => {
|
|
72
|
+
const User = db.collection({
|
|
73
|
+
name: 'users',
|
|
74
|
+
fields: [
|
|
75
|
+
{ type: 'string', name: 'name' },
|
|
76
|
+
{ type: 'hasMany', name: 'posts' },
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const Post = db.collection({
|
|
81
|
+
name: 'posts',
|
|
82
|
+
fields: [{ type: 'string', name: 'title' }],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await db.sync();
|
|
86
|
+
|
|
87
|
+
await User.repository.create({
|
|
88
|
+
values: [
|
|
89
|
+
{
|
|
90
|
+
name: 'u1',
|
|
91
|
+
posts: [{ title: 'u1p1' }, { title: 'u1p2' }],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'u2',
|
|
95
|
+
posts: [{ title: 'u2p1' }, { title: 'u2p2' }],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const findOptions = User.repository.buildQueryOptions({
|
|
101
|
+
appends: ['posts'],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
|
|
105
|
+
model: User.model,
|
|
106
|
+
rootAttributes: findOptions.attributes,
|
|
107
|
+
includeOption: findOptions.include,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await eagerLoadingTree.load([1, 2]);
|
|
111
|
+
|
|
112
|
+
const root = eagerLoadingTree.root;
|
|
113
|
+
const u1 = root.instances.find((item) => item.get('name') === 'u1');
|
|
114
|
+
const u1Posts = u1.get('posts') as any;
|
|
115
|
+
expect(u1Posts.length).toBe(2);
|
|
116
|
+
|
|
117
|
+
const u1JSON = u1.toJSON();
|
|
118
|
+
expect(u1JSON['posts'].length).toBe(2);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should load has one', async () => {
|
|
122
|
+
const User = db.collection({
|
|
123
|
+
name: 'users',
|
|
124
|
+
fields: [
|
|
125
|
+
{ type: 'string', name: 'name' },
|
|
126
|
+
{ type: 'hasOne', name: 'profile' },
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const Profile = db.collection({
|
|
131
|
+
name: 'profiles',
|
|
132
|
+
fields: [{ type: 'integer', name: 'age' }],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await db.sync();
|
|
136
|
+
|
|
137
|
+
const users = await User.repository.create({
|
|
138
|
+
values: [
|
|
139
|
+
{
|
|
140
|
+
name: 'u1',
|
|
141
|
+
profile: { age: 1 },
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'u2',
|
|
145
|
+
profile: { age: 2 },
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const findOptions = User.repository.buildQueryOptions({
|
|
151
|
+
appends: ['profile'],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
|
|
155
|
+
model: User.model,
|
|
156
|
+
rootAttributes: findOptions.attributes,
|
|
157
|
+
includeOption: findOptions.include,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await eagerLoadingTree.load(users.map((item) => item.id));
|
|
161
|
+
|
|
162
|
+
const root = eagerLoadingTree.root;
|
|
163
|
+
const u1 = root.instances.find((item) => item.get('name') === 'u1');
|
|
164
|
+
const u1Profile = u1.get('profile') as any;
|
|
165
|
+
expect(u1Profile).toBeDefined();
|
|
166
|
+
expect(u1Profile.get('age')).toBe(1);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should load belongs to', async () => {
|
|
170
|
+
const Post = db.collection({
|
|
171
|
+
name: 'posts',
|
|
172
|
+
fields: [
|
|
173
|
+
{ type: 'string', name: 'title' },
|
|
174
|
+
{
|
|
175
|
+
type: 'belongsTo',
|
|
176
|
+
name: 'user',
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const User = db.collection({
|
|
182
|
+
name: 'users',
|
|
183
|
+
fields: [{ type: 'string', name: 'name' }],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await db.sync();
|
|
187
|
+
|
|
188
|
+
await Post.repository.create({
|
|
189
|
+
values: [
|
|
190
|
+
{
|
|
191
|
+
title: 'p1',
|
|
192
|
+
user: {
|
|
193
|
+
name: 'u1',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
title: 'p2',
|
|
198
|
+
user: {
|
|
199
|
+
name: 'u2',
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const findOptions = Post.repository.buildQueryOptions({
|
|
206
|
+
appends: ['user'],
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
|
|
210
|
+
model: Post.model,
|
|
211
|
+
rootAttributes: findOptions.attributes,
|
|
212
|
+
includeOption: findOptions.include,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await eagerLoadingTree.load([1, 2]);
|
|
216
|
+
|
|
217
|
+
const root = eagerLoadingTree.root;
|
|
218
|
+
const p1 = root.instances.find((item) => item.get('title') === 'p1');
|
|
219
|
+
const p1User = p1.get('user') as any;
|
|
220
|
+
expect(p1User).toBeDefined();
|
|
221
|
+
expect(p1User.get('name')).toBe('u1');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should load belongs to many', async () => {
|
|
225
|
+
const Post = db.collection({
|
|
226
|
+
name: 'posts',
|
|
227
|
+
fields: [
|
|
228
|
+
{ type: 'string', name: 'title' },
|
|
229
|
+
{ type: 'belongsToMany', name: 'tags' },
|
|
230
|
+
],
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const Tag = db.collection({
|
|
234
|
+
name: 'tags',
|
|
235
|
+
fields: [{ type: 'string', name: 'name' }],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await db.sync();
|
|
239
|
+
|
|
240
|
+
const tags = await Tag.repository.create({
|
|
241
|
+
values: [
|
|
242
|
+
{
|
|
243
|
+
name: 't1',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 't2',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: 't3',
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await Post.repository.create({
|
|
255
|
+
values: [
|
|
256
|
+
{
|
|
257
|
+
title: 'p1',
|
|
258
|
+
tags: [{ id: tags[0].id }, { id: tags[1].id }],
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
title: 'p2',
|
|
262
|
+
tags: [{ id: tags[1].id }, { id: tags[2].id }],
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const findOptions = Post.repository.buildQueryOptions({
|
|
268
|
+
appends: ['tags'],
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
|
|
272
|
+
model: Post.model,
|
|
273
|
+
rootAttributes: findOptions.attributes,
|
|
274
|
+
includeOption: findOptions.include,
|
|
275
|
+
});
|
|
276
|
+
await eagerLoadingTree.load([1, 2]);
|
|
277
|
+
const root = eagerLoadingTree.root;
|
|
278
|
+
|
|
279
|
+
const p1 = root.instances.find((item) => item.get('title') === 'p1');
|
|
280
|
+
const p1Tags = p1.get('tags') as any;
|
|
281
|
+
expect(p1Tags).toBeDefined();
|
|
282
|
+
expect(p1Tags.length).toBe(2);
|
|
283
|
+
expect(p1Tags.map((t) => t.get('name'))).toEqual(['t1', 't2']);
|
|
284
|
+
|
|
285
|
+
const p2 = root.instances.find((item) => item.get('title') === 'p2');
|
|
286
|
+
const p2Tags = p2.get('tags') as any;
|
|
287
|
+
expect(p2Tags).toBeDefined();
|
|
288
|
+
expect(p2Tags.length).toBe(2);
|
|
289
|
+
expect(p2Tags.map((t) => t.get('name'))).toEqual(['t2', 't3']);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should build eager loading tree', async () => {
|
|
293
|
+
const User = db.collection({
|
|
294
|
+
name: 'users',
|
|
295
|
+
fields: [
|
|
296
|
+
{
|
|
297
|
+
type: 'string',
|
|
298
|
+
name: 'name',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
type: 'hasMany',
|
|
302
|
+
name: 'posts',
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const Post = db.collection({
|
|
308
|
+
name: 'posts',
|
|
309
|
+
fields: [
|
|
310
|
+
{
|
|
311
|
+
type: 'array',
|
|
312
|
+
name: 'tags',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
type: 'string',
|
|
316
|
+
name: 'title',
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
type: 'belongsToMany',
|
|
320
|
+
name: 'tags',
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const Tag = db.collection({
|
|
326
|
+
name: 'tags',
|
|
327
|
+
fields: [
|
|
328
|
+
{ type: 'string', name: 'name' },
|
|
329
|
+
{ type: 'belongsTo', name: 'tagCategory' },
|
|
330
|
+
],
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const TagCategory = db.collection({
|
|
334
|
+
name: 'tagCategories',
|
|
335
|
+
fields: [{ type: 'string', name: 'name' }],
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await db.sync();
|
|
339
|
+
|
|
340
|
+
await User.repository.create({
|
|
341
|
+
values: [
|
|
342
|
+
{
|
|
343
|
+
name: 'u1',
|
|
344
|
+
posts: [
|
|
345
|
+
{
|
|
346
|
+
title: 'u1p1',
|
|
347
|
+
tags: [
|
|
348
|
+
{ name: 't1', tagCategory: { name: 'c1' } },
|
|
349
|
+
{ name: 't2', tagCategory: { name: 'c2' } },
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: 'u2',
|
|
356
|
+
posts: [
|
|
357
|
+
{
|
|
358
|
+
title: 'u2p1',
|
|
359
|
+
tags: [
|
|
360
|
+
{ name: 't3', tagCategory: { name: 'c3' } },
|
|
361
|
+
{ name: 't4', tagCategory: { name: 'c4' } },
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const findOptions = User.repository.buildQueryOptions({
|
|
370
|
+
appends: ['posts.tags.tagCategory'],
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
|
|
374
|
+
model: User.model,
|
|
375
|
+
rootAttributes: findOptions.attributes,
|
|
376
|
+
includeOption: findOptions.include,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(eagerLoadingTree.root.children).toHaveLength(1);
|
|
380
|
+
expect(eagerLoadingTree.root.children[0].model).toBe(Post.model);
|
|
381
|
+
expect(eagerLoadingTree.root.children[0].children[0].model).toBe(Tag.model);
|
|
382
|
+
expect(eagerLoadingTree.root.children[0].children[0].children[0].model).toBe(TagCategory.model);
|
|
383
|
+
|
|
384
|
+
await eagerLoadingTree.load((await User.model.findAll()).map((item) => item[User.model.primaryKeyAttribute]));
|
|
385
|
+
|
|
386
|
+
expect(eagerLoadingTree.root.instances).toHaveLength(2);
|
|
387
|
+
const u1 = eagerLoadingTree.root.instances.find((item) => item.get('name') === 'u1');
|
|
388
|
+
expect(u1.get('posts')).toHaveLength(1);
|
|
389
|
+
expect(u1.get('posts')[0].get('tags')).toHaveLength(2);
|
|
390
|
+
expect(u1.get('posts')[0].get('tags')[0].get('tagCategory')).toBeDefined();
|
|
391
|
+
expect(u1.get('posts')[0].get('tags')[0].get('tagCategory').get('name')).toBe('c1');
|
|
392
|
+
});
|
|
393
|
+
});
|
|
@@ -18,6 +18,10 @@ describe('migrator', () => {
|
|
|
18
18
|
await db.close();
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
test('migrations', async () => {
|
|
22
|
+
expect(db.getModel('migrations').tableName).toBe('test_migrations');
|
|
23
|
+
});
|
|
24
|
+
|
|
21
25
|
test('addMigrations', async () => {
|
|
22
26
|
db.addMigrations({
|
|
23
27
|
directory: resolve(__dirname, './fixtures/migrations'),
|