@loopback/sequelize 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/README.md +194 -0
- package/dist/.sandbox/6646miobBk/application.js +15 -0
- package/dist/.sandbox/6646miobBk/controllers/book-category.controller.js +41 -0
- package/dist/.sandbox/6646miobBk/controllers/book.controller.js +210 -0
- package/dist/.sandbox/6646miobBk/controllers/category.controller.js +198 -0
- package/dist/.sandbox/6646miobBk/controllers/developer.controller.js +177 -0
- package/dist/.sandbox/6646miobBk/controllers/doctor-patient.controller.js +112 -0
- package/dist/.sandbox/6646miobBk/controllers/doctor.controller.js +177 -0
- package/dist/.sandbox/6646miobBk/controllers/index.js +20 -0
- package/dist/.sandbox/6646miobBk/controllers/patient.controller.js +165 -0
- package/dist/.sandbox/6646miobBk/controllers/programming-languange.controller.js +204 -0
- package/dist/.sandbox/6646miobBk/controllers/test.controller.base.js +25 -0
- package/dist/.sandbox/6646miobBk/controllers/todo-list-todo.controller.js +113 -0
- package/dist/.sandbox/6646miobBk/controllers/todo-list.controller.js +177 -0
- package/dist/.sandbox/6646miobBk/controllers/todo-todo-list.controller.js +41 -0
- package/dist/.sandbox/6646miobBk/controllers/todo.controller.js +177 -0
- package/dist/.sandbox/6646miobBk/controllers/user-todo-list.controller.js +113 -0
- package/dist/.sandbox/6646miobBk/controllers/user.controller.js +210 -0
- package/dist/.sandbox/6646miobBk/datasources/db.datasource.js +28 -0
- package/dist/.sandbox/6646miobBk/models/appointment.model.js +37 -0
- package/dist/.sandbox/6646miobBk/models/book.model.js +48 -0
- package/dist/.sandbox/6646miobBk/models/category.model.js +32 -0
- package/dist/.sandbox/6646miobBk/models/developer.model.js +40 -0
- package/dist/.sandbox/6646miobBk/models/doctor.model.js +44 -0
- package/dist/.sandbox/6646miobBk/models/index.js +15 -0
- package/dist/.sandbox/6646miobBk/models/patient.model.js +32 -0
- package/dist/.sandbox/6646miobBk/models/programming-language.model.js +32 -0
- package/dist/.sandbox/6646miobBk/models/todo-list.model.js +45 -0
- package/dist/.sandbox/6646miobBk/models/todo.model.js +44 -0
- package/dist/.sandbox/6646miobBk/models/user.model.js +85 -0
- package/dist/.sandbox/6646miobBk/repositories/appointment.repository.js +20 -0
- package/dist/.sandbox/6646miobBk/repositories/book.repository.js +25 -0
- package/dist/.sandbox/6646miobBk/repositories/category.repository.js +20 -0
- package/dist/.sandbox/6646miobBk/repositories/developer.repository.js +25 -0
- package/dist/.sandbox/6646miobBk/repositories/doctor.repository.js +27 -0
- package/dist/.sandbox/6646miobBk/repositories/index.js +15 -0
- package/dist/.sandbox/6646miobBk/repositories/patient.repository.js +20 -0
- package/dist/.sandbox/6646miobBk/repositories/programming-language.repository.js +20 -0
- package/dist/.sandbox/6646miobBk/repositories/todo-list.repository.js +25 -0
- package/dist/.sandbox/6646miobBk/repositories/todo.repository.js +25 -0
- package/dist/.sandbox/6646miobBk/repositories/user.repository.js +29 -0
- package/dist/component.d.ts +7 -0
- package/dist/component.js +30 -0
- package/dist/component.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +8 -0
- package/dist/keys.js +16 -0
- package/dist/keys.js.map +1 -0
- package/dist/sequelize/connector-mapping.d.ts +9 -0
- package/dist/sequelize/connector-mapping.js +19 -0
- package/dist/sequelize/connector-mapping.js.map +1 -0
- package/dist/sequelize/index.d.ts +2 -0
- package/dist/sequelize/index.js +10 -0
- package/dist/sequelize/index.js.map +1 -0
- package/dist/sequelize/operator-translation.d.ts +8 -0
- package/dist/sequelize/operator-translation.js +31 -0
- package/dist/sequelize/operator-translation.js.map +1 -0
- package/dist/sequelize/sequelize.datasource.base.d.ts +23 -0
- package/dist/sequelize/sequelize.datasource.base.js +60 -0
- package/dist/sequelize/sequelize.datasource.base.js.map +1 -0
- package/dist/sequelize/sequelize.model.d.ts +7 -0
- package/dist/sequelize/sequelize.model.js +24 -0
- package/dist/sequelize/sequelize.model.js.map +1 -0
- package/dist/sequelize/sequelize.repository.base.d.ts +231 -0
- package/dist/sequelize/sequelize.repository.base.js +835 -0
- package/dist/sequelize/sequelize.repository.base.js.map +1 -0
- package/dist/sequelize/utils.d.ts +6 -0
- package/dist/sequelize/utils.js +17 -0
- package/dist/sequelize/utils.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
- package/src/component.ts +34 -0
- package/src/index.ts +9 -0
- package/src/keys.ts +16 -0
- package/src/sequelize/connector-mapping.ts +26 -0
- package/src/sequelize/index.ts +7 -0
- package/src/sequelize/operator-translation.ts +32 -0
- package/src/sequelize/sequelize.datasource.base.ts +81 -0
- package/src/sequelize/sequelize.model.ts +22 -0
- package/src/sequelize/sequelize.repository.base.ts +1246 -0
- package/src/sequelize/utils.ts +13 -0
- package/src/types.ts +19 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright LoopBack contributors 2022. All Rights Reserved.
|
|
3
|
+
// Node module: @loopback/sequelize
|
|
4
|
+
// This file is licensed under the MIT License.
|
|
5
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.SequelizeCrudRepository = void 0;
|
|
8
|
+
const tslib_1 = require("tslib");
|
|
9
|
+
const repository_1 = require("@loopback/repository");
|
|
10
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
11
|
+
const sequelize_1 = require("sequelize");
|
|
12
|
+
const operator_translation_1 = require("./operator-translation");
|
|
13
|
+
const utils_1 = require("./utils");
|
|
14
|
+
const debug = (0, debug_1.default)('loopback:sequelize:repository');
|
|
15
|
+
const debugModelBuilder = (0, debug_1.default)('loopback:sequelize:modelbuilder');
|
|
16
|
+
/**
|
|
17
|
+
* Sequelize implementation of CRUD repository to be used with default loopback entities
|
|
18
|
+
* and SequelizeDataSource for SQL Databases
|
|
19
|
+
*/
|
|
20
|
+
class SequelizeCrudRepository {
|
|
21
|
+
constructor(entityClass, dataSource) {
|
|
22
|
+
this.entityClass = entityClass;
|
|
23
|
+
this.dataSource = dataSource;
|
|
24
|
+
/**
|
|
25
|
+
* Default `order` filter style if only column name is specified
|
|
26
|
+
*/
|
|
27
|
+
this.DEFAULT_ORDER_STYLE = 'ASC';
|
|
28
|
+
/**
|
|
29
|
+
* Object keys used in models for set database specific settings.
|
|
30
|
+
* Example: In model property definition one can use postgresql dataType as float
|
|
31
|
+
* {
|
|
32
|
+
* type: 'number',
|
|
33
|
+
* postgresql: {
|
|
34
|
+
* dataType: 'float',
|
|
35
|
+
* precision: 20,
|
|
36
|
+
* scale: 4,
|
|
37
|
+
* },
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* This array of keys is used while building model definition for sequelize.
|
|
41
|
+
*/
|
|
42
|
+
this.DB_SPECIFIC_SETTINGS_KEYS = [
|
|
43
|
+
'postgresql',
|
|
44
|
+
'mysql',
|
|
45
|
+
'sqlite3',
|
|
46
|
+
];
|
|
47
|
+
this.inclusionResolvers = new Map();
|
|
48
|
+
if (this.dataSource.sequelize) {
|
|
49
|
+
this.sequelizeModel = this.getSequelizeModel();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async create(entity, options) {
|
|
53
|
+
let err = null;
|
|
54
|
+
const data = await this.sequelizeModel
|
|
55
|
+
.create(entity, options)
|
|
56
|
+
.catch(error => {
|
|
57
|
+
console.error(error);
|
|
58
|
+
err = error;
|
|
59
|
+
});
|
|
60
|
+
if (!data) {
|
|
61
|
+
throw new Error(err !== null && err !== void 0 ? err : 'Something went wrong');
|
|
62
|
+
}
|
|
63
|
+
return new this.entityClass(this.excludeHiddenProps(data.toJSON()));
|
|
64
|
+
}
|
|
65
|
+
async createAll(entities, options) {
|
|
66
|
+
const models = await this.sequelizeModel.bulkCreate(entities, options);
|
|
67
|
+
return this.toEntities(models);
|
|
68
|
+
}
|
|
69
|
+
exists(id, _options) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
this.sequelizeModel
|
|
72
|
+
.findByPk(id)
|
|
73
|
+
.then(value => {
|
|
74
|
+
resolve(!!value);
|
|
75
|
+
})
|
|
76
|
+
.catch(err => {
|
|
77
|
+
reject(err);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async save(entity, options) {
|
|
82
|
+
const id = this.entityClass.getIdOf(entity);
|
|
83
|
+
if (id == null) {
|
|
84
|
+
return this.create(entity, options);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
await this.replaceById(id, entity, options);
|
|
88
|
+
return new this.entityClass(entity.toObject());
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
update(entity, options) {
|
|
92
|
+
return this.updateById(entity.getId(), entity, options);
|
|
93
|
+
}
|
|
94
|
+
async updateById(id, data, options) {
|
|
95
|
+
if (id === undefined) {
|
|
96
|
+
throw new Error('Invalid Argument: id cannot be undefined');
|
|
97
|
+
}
|
|
98
|
+
const idProp = this.entityClass.definition.idProperties()[0];
|
|
99
|
+
const where = {};
|
|
100
|
+
where[idProp] = id;
|
|
101
|
+
const result = await this.updateAll(data, where, options);
|
|
102
|
+
if (result.count === 0) {
|
|
103
|
+
throw new repository_1.EntityNotFoundError(this.entityClass, id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async updateAll(data, where, options) {
|
|
107
|
+
const [affectedCount] = await this.sequelizeModel.update(Object.assign({}, data), {
|
|
108
|
+
where: this.buildSequelizeWhere(where),
|
|
109
|
+
...options,
|
|
110
|
+
});
|
|
111
|
+
return { count: affectedCount };
|
|
112
|
+
}
|
|
113
|
+
async delete(entity, options) {
|
|
114
|
+
return this.deleteById(entity.getId(), options);
|
|
115
|
+
}
|
|
116
|
+
async find(filter, options) {
|
|
117
|
+
var _a;
|
|
118
|
+
const data = await this.sequelizeModel
|
|
119
|
+
.findAll({
|
|
120
|
+
include: this.buildSequelizeIncludeFilter(filter === null || filter === void 0 ? void 0 : filter.include),
|
|
121
|
+
where: this.buildSequelizeWhere(filter === null || filter === void 0 ? void 0 : filter.where),
|
|
122
|
+
attributes: this.buildSequelizeAttributeFilter(filter === null || filter === void 0 ? void 0 : filter.fields),
|
|
123
|
+
order: this.buildSequelizeOrder(filter === null || filter === void 0 ? void 0 : filter.order),
|
|
124
|
+
limit: filter === null || filter === void 0 ? void 0 : filter.limit,
|
|
125
|
+
offset: (_a = filter === null || filter === void 0 ? void 0 : filter.offset) !== null && _a !== void 0 ? _a : filter === null || filter === void 0 ? void 0 : filter.skip,
|
|
126
|
+
...options,
|
|
127
|
+
})
|
|
128
|
+
.catch(err => {
|
|
129
|
+
debug('findAll() error:', err);
|
|
130
|
+
throw new Error(err);
|
|
131
|
+
});
|
|
132
|
+
return this.includeReferencesIfRequested(data, this.entityClass, filter === null || filter === void 0 ? void 0 : filter.include);
|
|
133
|
+
}
|
|
134
|
+
async findOne(filter, options) {
|
|
135
|
+
var _a;
|
|
136
|
+
const data = await this.sequelizeModel
|
|
137
|
+
.findOne({
|
|
138
|
+
include: this.buildSequelizeIncludeFilter(filter === null || filter === void 0 ? void 0 : filter.include),
|
|
139
|
+
where: this.buildSequelizeWhere(filter === null || filter === void 0 ? void 0 : filter.where),
|
|
140
|
+
attributes: this.buildSequelizeAttributeFilter(filter === null || filter === void 0 ? void 0 : filter.fields),
|
|
141
|
+
order: this.buildSequelizeOrder(filter === null || filter === void 0 ? void 0 : filter.order),
|
|
142
|
+
offset: (_a = filter === null || filter === void 0 ? void 0 : filter.offset) !== null && _a !== void 0 ? _a : filter === null || filter === void 0 ? void 0 : filter.skip,
|
|
143
|
+
...options,
|
|
144
|
+
})
|
|
145
|
+
.catch(err => {
|
|
146
|
+
debug('findOne() error:', err);
|
|
147
|
+
throw new Error(err);
|
|
148
|
+
});
|
|
149
|
+
if (data === null) {
|
|
150
|
+
return Promise.resolve(null);
|
|
151
|
+
}
|
|
152
|
+
const resolved = await this.includeReferencesIfRequested([data], this.entityClass, filter === null || filter === void 0 ? void 0 : filter.include);
|
|
153
|
+
return resolved[0];
|
|
154
|
+
}
|
|
155
|
+
async findById(id, filter, options) {
|
|
156
|
+
var _a;
|
|
157
|
+
const data = await this.sequelizeModel.findByPk(id, {
|
|
158
|
+
order: this.buildSequelizeOrder(filter === null || filter === void 0 ? void 0 : filter.order),
|
|
159
|
+
attributes: this.buildSequelizeAttributeFilter(filter === null || filter === void 0 ? void 0 : filter.fields),
|
|
160
|
+
include: this.buildSequelizeIncludeFilter(filter === null || filter === void 0 ? void 0 : filter.include),
|
|
161
|
+
limit: filter === null || filter === void 0 ? void 0 : filter.limit,
|
|
162
|
+
offset: (_a = filter === null || filter === void 0 ? void 0 : filter.offset) !== null && _a !== void 0 ? _a : filter === null || filter === void 0 ? void 0 : filter.skip,
|
|
163
|
+
...options,
|
|
164
|
+
});
|
|
165
|
+
if (!data) {
|
|
166
|
+
throw new repository_1.EntityNotFoundError(this.entityClass, id);
|
|
167
|
+
}
|
|
168
|
+
const resolved = await this.includeReferencesIfRequested([data], this.entityClass, filter === null || filter === void 0 ? void 0 : filter.include);
|
|
169
|
+
return resolved[0];
|
|
170
|
+
}
|
|
171
|
+
async replaceById(id, data, options) {
|
|
172
|
+
const idProp = this.entityClass.definition.idProperties()[0];
|
|
173
|
+
if (idProp in data) {
|
|
174
|
+
delete data[idProp];
|
|
175
|
+
}
|
|
176
|
+
await this.updateById(id, data, options);
|
|
177
|
+
}
|
|
178
|
+
async deleteAll(where, options) {
|
|
179
|
+
const count = await this.sequelizeModel.destroy({
|
|
180
|
+
where: this.buildSequelizeWhere(where),
|
|
181
|
+
...options,
|
|
182
|
+
});
|
|
183
|
+
return { count };
|
|
184
|
+
}
|
|
185
|
+
async deleteById(id, options) {
|
|
186
|
+
const idProp = this.entityClass.definition.idProperties()[0];
|
|
187
|
+
if (id === undefined) {
|
|
188
|
+
throw new Error(`Invalid Argument: ${idProp} cannot be undefined`);
|
|
189
|
+
}
|
|
190
|
+
const where = {};
|
|
191
|
+
where[idProp] = id;
|
|
192
|
+
const count = await this.sequelizeModel.destroy({
|
|
193
|
+
where: this.buildSequelizeWhere(where),
|
|
194
|
+
...options,
|
|
195
|
+
});
|
|
196
|
+
if (count === 0) {
|
|
197
|
+
throw new repository_1.EntityNotFoundError(this.entityClass, id);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async count(where, options) {
|
|
201
|
+
const count = await this.sequelizeModel.count({
|
|
202
|
+
where: this.buildSequelizeWhere(where),
|
|
203
|
+
...options,
|
|
204
|
+
});
|
|
205
|
+
return { count };
|
|
206
|
+
}
|
|
207
|
+
async execute(..._args) {
|
|
208
|
+
throw new Error('RAW Query execution is currently NOT supported for Sequelize CRUD Repository.');
|
|
209
|
+
}
|
|
210
|
+
toEntities(models) {
|
|
211
|
+
return models.map(m => new this.entityClass(m.toJSON()));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get Sequelize Operator
|
|
215
|
+
* @param key Name of the operator used in loopback eg. lt
|
|
216
|
+
* @returns Equivalent operator symbol if available in Sequelize eg `Op.lt`
|
|
217
|
+
*/
|
|
218
|
+
getSequelizeOperator(key) {
|
|
219
|
+
const sequelizeOperator = operator_translation_1.operatorTranslations[key];
|
|
220
|
+
if (!sequelizeOperator) {
|
|
221
|
+
throw Error(`There is no equivalent operator for "${key}" in sequelize.`);
|
|
222
|
+
}
|
|
223
|
+
return sequelizeOperator;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get Sequelize `attributes` filter value from `fields` of loopback.
|
|
227
|
+
* @param fields Loopback styles `fields` options. eg. `["name", "age"]`, `{ id: false }`
|
|
228
|
+
* @returns Sequelize Compatible Object/Array based on the fields provided. eg. `{ "exclude": ["id"] }`
|
|
229
|
+
*/
|
|
230
|
+
buildSequelizeAttributeFilter(fields) {
|
|
231
|
+
var _a, _b;
|
|
232
|
+
if (fields === undefined) {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
if (Array.isArray(fields)) {
|
|
236
|
+
// Both (sequelize and loopback filters) consider array as "only columns to include"
|
|
237
|
+
return fields;
|
|
238
|
+
}
|
|
239
|
+
const sequelizeFields = {
|
|
240
|
+
include: [],
|
|
241
|
+
exclude: [],
|
|
242
|
+
};
|
|
243
|
+
// Push column having `false` values in `exclude` key and columns
|
|
244
|
+
// having `true` in `include` key
|
|
245
|
+
if ((0, utils_1.isTruelyObject)(fields)) {
|
|
246
|
+
for (const key in fields) {
|
|
247
|
+
if (fields[key] === true) {
|
|
248
|
+
(_a = sequelizeFields.include) === null || _a === void 0 ? void 0 : _a.push(key);
|
|
249
|
+
}
|
|
250
|
+
else if (fields[key] === false) {
|
|
251
|
+
(_b = sequelizeFields.exclude) === null || _b === void 0 ? void 0 : _b.push(key);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (Array.isArray(sequelizeFields.include) &&
|
|
256
|
+
sequelizeFields.include.length > 0) {
|
|
257
|
+
delete sequelizeFields.exclude;
|
|
258
|
+
return sequelizeFields.include;
|
|
259
|
+
}
|
|
260
|
+
if (Array.isArray(sequelizeFields.exclude) &&
|
|
261
|
+
sequelizeFields.exclude.length > 0) {
|
|
262
|
+
delete sequelizeFields.include;
|
|
263
|
+
}
|
|
264
|
+
return sequelizeFields;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get Sequelize Order filter value from loopback style order value
|
|
268
|
+
* @param order Sorting order in loopback style filter. eg. `title ASC`, `["id DESC", "age ASC"]`
|
|
269
|
+
* @returns Sequelize compatible order filter value
|
|
270
|
+
*/
|
|
271
|
+
buildSequelizeOrder(order) {
|
|
272
|
+
if (order === undefined) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
if (typeof order === 'string') {
|
|
276
|
+
const [columnName, orderType] = order.trim().split(' ');
|
|
277
|
+
return [[columnName, orderType !== null && orderType !== void 0 ? orderType : this.DEFAULT_ORDER_STYLE]];
|
|
278
|
+
}
|
|
279
|
+
return order.map(orderStr => {
|
|
280
|
+
const [columnName, orderType] = orderStr.trim().split(' ');
|
|
281
|
+
return [columnName, orderType !== null && orderType !== void 0 ? orderType : this.DEFAULT_ORDER_STYLE];
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Build Sequelize compatible `include` filter
|
|
286
|
+
* @param inclusionFilters - loopback style `where` condition
|
|
287
|
+
* @param sourceModel - sequelize model instance
|
|
288
|
+
* @returns Sequelize compatible `Includeable` array
|
|
289
|
+
*/
|
|
290
|
+
buildSequelizeIncludeFilter(inclusionFilters, sourceModel) {
|
|
291
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
292
|
+
if (!inclusionFilters || inclusionFilters.length === 0) {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
if (!sourceModel) {
|
|
296
|
+
sourceModel = this.sequelizeModel;
|
|
297
|
+
}
|
|
298
|
+
const includable = [];
|
|
299
|
+
for (const filter of inclusionFilters) {
|
|
300
|
+
if (typeof filter === 'string') {
|
|
301
|
+
if (filter in sourceModel.associations) {
|
|
302
|
+
includable.push(filter);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
debug(`Relation '${filter}' is not available in sequelize model associations. If it's referencesMany relation it will fallback to loopback inclusion resolver.`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else if (typeof filter === 'object') {
|
|
309
|
+
if (!(filter.relation in sourceModel.associations)) {
|
|
310
|
+
debug(`Relation '${filter.relation}' is not available in sequelize model associations. If it's referencesMany relation it will fallback to loopback inclusion resolver.`);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const targetAssociation = sourceModel.associations[filter.relation];
|
|
314
|
+
includable.push({
|
|
315
|
+
model: targetAssociation.target,
|
|
316
|
+
/**
|
|
317
|
+
* Exclude through model data from response to be backward compatible
|
|
318
|
+
* with loopback response style for hasMany through relation.
|
|
319
|
+
* Does not work with sqlite3
|
|
320
|
+
*/
|
|
321
|
+
...(targetAssociation.associationType === 'BelongsToMany' &&
|
|
322
|
+
targetAssociation.isMultiAssociation
|
|
323
|
+
? { through: { attributes: [] } }
|
|
324
|
+
: {}),
|
|
325
|
+
where: this.buildSequelizeWhere((_a = filter.scope) === null || _a === void 0 ? void 0 : _a.where),
|
|
326
|
+
limit: (_c = (_b = filter.scope) === null || _b === void 0 ? void 0 : _b.totalLimit) !== null && _c !== void 0 ? _c : (_d = filter.scope) === null || _d === void 0 ? void 0 : _d.limit,
|
|
327
|
+
attributes: this.buildSequelizeAttributeFilter((_e = filter.scope) === null || _e === void 0 ? void 0 : _e.fields),
|
|
328
|
+
include: this.buildSequelizeIncludeFilter((_f = filter.scope) === null || _f === void 0 ? void 0 : _f.include, targetAssociation.target),
|
|
329
|
+
order: this.buildSequelizeOrder((_g = filter.scope) === null || _g === void 0 ? void 0 : _g.order),
|
|
330
|
+
as: filter.relation,
|
|
331
|
+
/**
|
|
332
|
+
* If true, uses an inner join, which means that the parent model will only be loaded if it has any matching children.
|
|
333
|
+
*/
|
|
334
|
+
required: !!filter.required,
|
|
335
|
+
/**
|
|
336
|
+
* saperate: true is required for `order` and `limit` filter to work, it runs include in saperate queries
|
|
337
|
+
*/
|
|
338
|
+
separate: !!((_h = filter.scope) === null || _h === void 0 ? void 0 : _h.order) ||
|
|
339
|
+
!!((_k = (_j = filter.scope) === null || _j === void 0 ? void 0 : _j.totalLimit) !== null && _k !== void 0 ? _k : (_l = filter.scope) === null || _l === void 0 ? void 0 : _l.limit),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return includable;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Build Sequelize compatible where condition object
|
|
347
|
+
* @param where loopback style `where` condition
|
|
348
|
+
* @returns Sequelize compatible where options to be used in queries
|
|
349
|
+
*/
|
|
350
|
+
buildSequelizeWhere(where) {
|
|
351
|
+
if (!where) {
|
|
352
|
+
return {};
|
|
353
|
+
}
|
|
354
|
+
const sequelizeWhere = {};
|
|
355
|
+
/**
|
|
356
|
+
* Handle model attribute conditions like `{ age: { gt: 18 } }`, `{ email: "a@b.c" }`
|
|
357
|
+
* Transform Operators - eg. `{ gt: 0, lt: 10 }` to `{ [Op.gt]: 0, [Op.lt]: 10 }`
|
|
358
|
+
*/
|
|
359
|
+
for (const columnName in where) {
|
|
360
|
+
const conditionValue = (where[columnName]);
|
|
361
|
+
if ((0, utils_1.isTruelyObject)(conditionValue)) {
|
|
362
|
+
sequelizeWhere[columnName] = {};
|
|
363
|
+
for (const lb4Operator of Object.keys(conditionValue)) {
|
|
364
|
+
const sequelizeOperator = this.getSequelizeOperator(lb4Operator);
|
|
365
|
+
sequelizeWhere[columnName][sequelizeOperator] =
|
|
366
|
+
conditionValue[lb4Operator];
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else if (['and', 'or'].includes(columnName) &&
|
|
370
|
+
Array.isArray(conditionValue)) {
|
|
371
|
+
/**
|
|
372
|
+
* Eg. {and: [{title: 'My Post'}, {content: 'Hello'}]}
|
|
373
|
+
*/
|
|
374
|
+
const sequelizeOperator = this.getSequelizeOperator(columnName);
|
|
375
|
+
const conditions = conditionValue.map((condition) => {
|
|
376
|
+
return this.buildSequelizeWhere(condition);
|
|
377
|
+
});
|
|
378
|
+
Object.assign(sequelizeWhere, {
|
|
379
|
+
[sequelizeOperator]: conditions,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
// Equals
|
|
384
|
+
sequelizeWhere[columnName] = {
|
|
385
|
+
[sequelize_1.Op.eq]: conditionValue,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return sequelizeWhere;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get Sequelize Model
|
|
393
|
+
* @returns Sequelize Model Instance based on the definitions from `entityClass`
|
|
394
|
+
*/
|
|
395
|
+
getSequelizeModel(entityClass = this.entityClass) {
|
|
396
|
+
if (!this.dataSource.sequelize) {
|
|
397
|
+
throw Error(`The datasource "${this.dataSource.name}" doesn't have sequelize instance bound to it.`);
|
|
398
|
+
}
|
|
399
|
+
if (this.dataSource.sequelize.models[entityClass.modelName]) {
|
|
400
|
+
// Model Already Defined by Sequelize before
|
|
401
|
+
return this.dataSource.sequelize.models[entityClass.modelName];
|
|
402
|
+
}
|
|
403
|
+
// TODO: Make it more flexible, check support of all possible definition props
|
|
404
|
+
const sourceModel = this.dataSource.sequelize.define(entityClass.modelName, this.getSequelizeModelAttributes(entityClass.definition.properties), {
|
|
405
|
+
timestamps: false,
|
|
406
|
+
tableName: entityClass.modelName.toLowerCase(),
|
|
407
|
+
freezeTableName: true,
|
|
408
|
+
});
|
|
409
|
+
// Setup associations
|
|
410
|
+
for (const key in entityClass.definition.relations) {
|
|
411
|
+
const targetModel = this.getSequelizeModel(entityClass.definition.relations[key].target());
|
|
412
|
+
debugModelBuilder(`Setting up relation`, entityClass.definition.relations[key]);
|
|
413
|
+
if (entityClass.definition.relations[key].type ===
|
|
414
|
+
repository_1.RelationType.belongsTo) {
|
|
415
|
+
const foreignKey = entityClass.definition.relations[key].keyTo;
|
|
416
|
+
sourceModel.belongsTo(targetModel, {
|
|
417
|
+
foreignKey: { name: foreignKey },
|
|
418
|
+
// Which client will pass on in loopback style include filter, eg. `include: ["thisName"]`
|
|
419
|
+
as: entityClass.definition.relations[key].name,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
else if (entityClass.definition.relations[key].type ===
|
|
423
|
+
repository_1.RelationType.hasOne) {
|
|
424
|
+
const foreignKey = entityClass.definition.relations[key].keyTo;
|
|
425
|
+
sourceModel.hasOne(targetModel, {
|
|
426
|
+
foreignKey: foreignKey,
|
|
427
|
+
as: entityClass.definition.relations[key].name,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
else if (entityClass.definition.relations[key].type ===
|
|
431
|
+
repository_1.RelationType.hasMany) {
|
|
432
|
+
const relationDefinition = entityClass.definition.relations[key];
|
|
433
|
+
const through = relationDefinition.through;
|
|
434
|
+
const foreignKey = relationDefinition.keyTo;
|
|
435
|
+
if (through) {
|
|
436
|
+
const keyTo = through.keyTo;
|
|
437
|
+
const keyFrom = through.keyFrom;
|
|
438
|
+
// Setup hasMany through
|
|
439
|
+
const throughModel = this.getSequelizeModel(through.model());
|
|
440
|
+
sourceModel.belongsToMany(targetModel, {
|
|
441
|
+
through: { model: throughModel },
|
|
442
|
+
otherKey: keyTo,
|
|
443
|
+
foreignKey: keyFrom,
|
|
444
|
+
as: entityClass.definition.relations[key].name,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
sourceModel.hasMany(targetModel, {
|
|
449
|
+
foreignKey: foreignKey,
|
|
450
|
+
as: entityClass.definition.relations[key].name,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
debugModelBuilder('Table name supplied to sequelize'.concat(`"${entityClass.modelName.toLowerCase()}"`));
|
|
456
|
+
return sourceModel;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Run CREATE TABLE query for the target sequelize model, Useful for quick testing
|
|
460
|
+
* @param options Sequelize Sync Options
|
|
461
|
+
*/
|
|
462
|
+
async syncSequelizeModel(options = {}) {
|
|
463
|
+
var _a;
|
|
464
|
+
await ((_a = this.dataSource.sequelize) === null || _a === void 0 ? void 0 : _a.models[this.entityClass.modelName].sync(options).catch(console.error));
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Run CREATE TABLE query for the all sequelize models, Useful for quick testing
|
|
468
|
+
* @param options Sequelize Sync Options
|
|
469
|
+
*/
|
|
470
|
+
async syncLoadedSequelizeModels(options = {}) {
|
|
471
|
+
var _a;
|
|
472
|
+
await ((_a = this.dataSource.sequelize) === null || _a === void 0 ? void 0 : _a.sync(options).catch(console.error));
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Get Sequelize Model Attributes
|
|
476
|
+
* @param definition property definition received from loopback entityClass eg. `{ id: { type: "Number", id: true } }`
|
|
477
|
+
* @returns model attributes supported in sequelize model definiotion
|
|
478
|
+
*
|
|
479
|
+
* TODO: Verify all possible loopback types https://loopback.io/doc/en/lb4/LoopBack-types.html
|
|
480
|
+
*/
|
|
481
|
+
getSequelizeModelAttributes(definition) {
|
|
482
|
+
var _a;
|
|
483
|
+
debugModelBuilder('loopback model definition', definition);
|
|
484
|
+
const sequelizeDefinition = {};
|
|
485
|
+
for (const propName in definition) {
|
|
486
|
+
// Set data type, defaults to `DataTypes.STRING`
|
|
487
|
+
let dataType = sequelize_1.DataTypes.STRING;
|
|
488
|
+
const isString = definition[propName].type === String ||
|
|
489
|
+
['String', 'string'].includes(definition[propName].type.toString());
|
|
490
|
+
if (definition[propName].type === Number ||
|
|
491
|
+
['Number', 'number'].includes(definition[propName].type.toString())) {
|
|
492
|
+
dataType = sequelize_1.DataTypes.NUMBER;
|
|
493
|
+
// handle float
|
|
494
|
+
for (const dbKey of this.DB_SPECIFIC_SETTINGS_KEYS) {
|
|
495
|
+
if (!(dbKey in definition[propName])) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const dbSpecificSetting = definition[propName][dbKey];
|
|
499
|
+
if (['double precision', 'float', 'real'].includes(dbSpecificSetting.dataType)) {
|
|
500
|
+
// TODO: Handle precision
|
|
501
|
+
dataType = sequelize_1.DataTypes.FLOAT;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (definition[propName].type === Boolean ||
|
|
506
|
+
['Boolean', 'boolean'].includes(definition[propName].type.toString())) {
|
|
507
|
+
dataType = sequelize_1.DataTypes.BOOLEAN;
|
|
508
|
+
}
|
|
509
|
+
if (definition[propName].type === Array ||
|
|
510
|
+
['Array', 'array'].includes(definition[propName].type.toString())) {
|
|
511
|
+
// Postgres only
|
|
512
|
+
dataType = sequelize_1.DataTypes.ARRAY(sequelize_1.DataTypes.INTEGER);
|
|
513
|
+
}
|
|
514
|
+
if (definition[propName].type === Object ||
|
|
515
|
+
['object', 'Object'].includes(definition[propName].type.toString())) {
|
|
516
|
+
// Postgres only, JSON dataType
|
|
517
|
+
dataType = sequelize_1.DataTypes.JSON;
|
|
518
|
+
}
|
|
519
|
+
if (definition[propName].type === Date ||
|
|
520
|
+
['date', 'Date'].includes(definition[propName].type.toString())) {
|
|
521
|
+
dataType = sequelize_1.DataTypes.DATE;
|
|
522
|
+
}
|
|
523
|
+
if (dataType === sequelize_1.DataTypes.STRING && !isString) {
|
|
524
|
+
throw Error(`Unhandled DataType "${definition[propName].type.toString()}" for column "${propName}" in sequelize extension`);
|
|
525
|
+
}
|
|
526
|
+
const columnOptions = {
|
|
527
|
+
type: dataType,
|
|
528
|
+
...('default' in definition[propName]
|
|
529
|
+
? { defaultValue: definition[propName].default }
|
|
530
|
+
: {}),
|
|
531
|
+
};
|
|
532
|
+
// Set column as `primaryKey` when id is set to true (which is loopback way to define primary key)
|
|
533
|
+
if (definition[propName].id === true) {
|
|
534
|
+
if (columnOptions.type === sequelize_1.DataTypes.NUMBER) {
|
|
535
|
+
columnOptions.type = sequelize_1.DataTypes.INTEGER;
|
|
536
|
+
}
|
|
537
|
+
Object.assign(columnOptions, {
|
|
538
|
+
primaryKey: true,
|
|
539
|
+
/**
|
|
540
|
+
* `autoIncrement` needs to be true even if DataType is not INTEGER else it will pass the ID in the query set to NULL.
|
|
541
|
+
*/
|
|
542
|
+
autoIncrement: !!definition[propName].generated,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
// TODO: Get the column name casing using actual methods / conventions used in different sql connectors for loopback
|
|
546
|
+
columnOptions.field =
|
|
547
|
+
(_a = definition[propName]['name']) !== null && _a !== void 0 ? _a : propName.toLowerCase();
|
|
548
|
+
sequelizeDefinition[propName] = columnOptions;
|
|
549
|
+
}
|
|
550
|
+
debugModelBuilder('Sequelize model definition', sequelizeDefinition);
|
|
551
|
+
return sequelizeDefinition;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Remove hidden properties specified in model from response body. (See: https://github.com/sourcefuse/loopback4-sequelize/issues/3)
|
|
555
|
+
* @param entity normalized entity. You can use `entity.toJSON()`'s value
|
|
556
|
+
* @returns normalized entity excluding the hiddenProperties
|
|
557
|
+
*/
|
|
558
|
+
excludeHiddenProps(entity) {
|
|
559
|
+
const hiddenProps = this.entityClass.definition.settings.hiddenProperties;
|
|
560
|
+
if (!hiddenProps) {
|
|
561
|
+
return entity;
|
|
562
|
+
}
|
|
563
|
+
for (const propertyName of hiddenProps) {
|
|
564
|
+
delete entity[propertyName];
|
|
565
|
+
}
|
|
566
|
+
return entity;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Include related entities of `@referencesMany` relation
|
|
570
|
+
*
|
|
571
|
+
* referencesMany relation is NOT handled by `sequelizeModel.findAll` as it doesn't have any direct alternative to it,
|
|
572
|
+
* so to include relation data of referencesMany, we're manually fetching related data requested
|
|
573
|
+
*
|
|
574
|
+
* @param parentEntities source table data
|
|
575
|
+
* @param filter actual payload passed in request
|
|
576
|
+
* @param parentEntityClass loopback entity class for the parent entity
|
|
577
|
+
* @returns entities with related models in them
|
|
578
|
+
*/
|
|
579
|
+
async includeReferencesIfRequested(parentEntities, parentEntityClass, inclusionFilters) {
|
|
580
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
581
|
+
if (!parentEntityClass) {
|
|
582
|
+
parentEntityClass = this.entityClass;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* All columns names defined in model with `@referencesMany`
|
|
586
|
+
*/
|
|
587
|
+
const allReferencesColumns = [];
|
|
588
|
+
for (const key in parentEntityClass.definition.relations) {
|
|
589
|
+
if (parentEntityClass.definition.relations[key].type ===
|
|
590
|
+
repository_1.RelationType.referencesMany) {
|
|
591
|
+
const loopbackRelationObject = parentEntityClass.definition.relations[key];
|
|
592
|
+
if (loopbackRelationObject.keyFrom) {
|
|
593
|
+
allReferencesColumns.push(loopbackRelationObject.keyFrom);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// Validate data type of items in any column having references
|
|
598
|
+
// For eg. convert ["1", "2"] into [1, 2] if `itemType` specified is `number[]`
|
|
599
|
+
const normalizedParentEntities = parentEntities.map(entity => {
|
|
600
|
+
const data = entity.toJSON();
|
|
601
|
+
for (const columnName in data) {
|
|
602
|
+
if (!allReferencesColumns.includes(columnName)) {
|
|
603
|
+
// Column is not the one used for referencesMany relation. Eg. "programmingLanguageIds"
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const columnDefinition = parentEntityClass.definition.properties[columnName];
|
|
607
|
+
if (columnDefinition.type !== Array ||
|
|
608
|
+
!Array.isArray(data[columnName])) {
|
|
609
|
+
// Column type or data received is not array, wrong configuration/data
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
// Loop over all references in array received
|
|
613
|
+
const items = data[columnName];
|
|
614
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
615
|
+
if (columnDefinition.itemType === Number &&
|
|
616
|
+
typeof items[itemIndex] === 'string') {
|
|
617
|
+
items[itemIndex] = parseInt(items[itemIndex]);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
data[columnName] = items;
|
|
621
|
+
}
|
|
622
|
+
return data;
|
|
623
|
+
});
|
|
624
|
+
// Requested inclusions of referencesMany relation
|
|
625
|
+
const referencesManyInclusions = [];
|
|
626
|
+
for (let includeFilter of inclusionFilters !== null && inclusionFilters !== void 0 ? inclusionFilters : []) {
|
|
627
|
+
if (typeof includeFilter === 'string') {
|
|
628
|
+
includeFilter = { relation: includeFilter };
|
|
629
|
+
}
|
|
630
|
+
const relationName = includeFilter.relation;
|
|
631
|
+
const relation = parentEntityClass.definition.relations[relationName];
|
|
632
|
+
if (relation.type === repository_1.RelationType.referencesMany) {
|
|
633
|
+
referencesManyInclusions.push({
|
|
634
|
+
filter: includeFilter,
|
|
635
|
+
definition: relation,
|
|
636
|
+
keys: [],
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (referencesManyInclusions.length === 0) {
|
|
641
|
+
const entityClasses = normalizedParentEntities.map(e => new parentEntityClass(e));
|
|
642
|
+
return entityClasses;
|
|
643
|
+
}
|
|
644
|
+
for (const relation of referencesManyInclusions) {
|
|
645
|
+
normalizedParentEntities.forEach(entity => {
|
|
646
|
+
if (!relation.definition.keyFrom) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const columnValue = entity[relation.definition.keyFrom];
|
|
650
|
+
if (Array.isArray(columnValue)) {
|
|
651
|
+
relation.keys.push(...columnValue);
|
|
652
|
+
}
|
|
653
|
+
else if (typeof columnValue === 'string' && columnValue.length > 0) {
|
|
654
|
+
relation.keys.push(...columnValue.split(','));
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
// column value holding references keys isn't an array nor a string
|
|
658
|
+
debug(`Column "${relation.definition.keyFrom}"'s value holding references keys isn't an array for ${JSON.stringify(entity)}, Can't fetch related models.`);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
relation.keys = [...new Set(relation.keys)];
|
|
662
|
+
const foreignKey = (_a = relation.definition.keyTo) !== null && _a !== void 0 ? _a : relation.definition.target().definition.idProperties()[0];
|
|
663
|
+
// Strictly include primary key in attributes
|
|
664
|
+
const attributesToFetch = this.buildSequelizeAttributeFilter((_b = relation.filter.scope) === null || _b === void 0 ? void 0 : _b.fields);
|
|
665
|
+
let includeForeignKeyInResponse = false;
|
|
666
|
+
if (attributesToFetch !== undefined) {
|
|
667
|
+
if (Array.isArray(attributesToFetch)) {
|
|
668
|
+
if (attributesToFetch.includes(foreignKey)) {
|
|
669
|
+
includeForeignKeyInResponse = true;
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
attributesToFetch.push(foreignKey);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else if (Array.isArray(attributesToFetch.include)) {
|
|
676
|
+
if (attributesToFetch.include.includes(foreignKey)) {
|
|
677
|
+
includeForeignKeyInResponse = true;
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
attributesToFetch.include.push(foreignKey);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
includeForeignKeyInResponse = true;
|
|
686
|
+
}
|
|
687
|
+
const targetLoopbackModel = relation.definition.target();
|
|
688
|
+
const targetSequelizeModel = this.getSequelizeModel(targetLoopbackModel);
|
|
689
|
+
const sequelizeData = await targetSequelizeModel.findAll({
|
|
690
|
+
where: {
|
|
691
|
+
// eg. id: { [Op.in]: [1,2,4,8] }
|
|
692
|
+
[foreignKey]: {
|
|
693
|
+
[sequelize_1.Op.in]: relation.keys,
|
|
694
|
+
},
|
|
695
|
+
...this.buildSequelizeWhere((_c = relation.filter.scope) === null || _c === void 0 ? void 0 : _c.where),
|
|
696
|
+
},
|
|
697
|
+
attributes: attributesToFetch,
|
|
698
|
+
include: this.buildSequelizeIncludeFilter((_d = relation.filter.scope) === null || _d === void 0 ? void 0 : _d.include, targetSequelizeModel),
|
|
699
|
+
order: this.buildSequelizeOrder((_e = relation.filter.scope) === null || _e === void 0 ? void 0 : _e.order),
|
|
700
|
+
limit: (_g = (_f = relation.filter.scope) === null || _f === void 0 ? void 0 : _f.totalLimit) !== null && _g !== void 0 ? _g : (_h = relation.filter.scope) === null || _h === void 0 ? void 0 : _h.limit,
|
|
701
|
+
offset: (_k = (_j = relation.filter.scope) === null || _j === void 0 ? void 0 : _j.offset) !== null && _k !== void 0 ? _k : (_l = relation.filter.scope) === null || _l === void 0 ? void 0 : _l.skip,
|
|
702
|
+
});
|
|
703
|
+
const childModelData = await this.includeReferencesIfRequested(sequelizeData, targetLoopbackModel, (_m = relation.filter.scope) === null || _m === void 0 ? void 0 : _m.include);
|
|
704
|
+
normalizedParentEntities.forEach(entity => {
|
|
705
|
+
const foreignKeys = entity[relation.definition.keyFrom];
|
|
706
|
+
const filteredChildModels = childModelData.filter(childModel => {
|
|
707
|
+
if (Array.isArray(foreignKeys)) {
|
|
708
|
+
return foreignKeys === null || foreignKeys === void 0 ? void 0 : foreignKeys.includes(childModel[foreignKey]);
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
Object.assign(entity, {
|
|
715
|
+
[relation.definition.name]: filteredChildModels.map(filteredChildModel => {
|
|
716
|
+
const safeCopy = { ...filteredChildModel };
|
|
717
|
+
if (includeForeignKeyInResponse === false) {
|
|
718
|
+
delete safeCopy[foreignKey];
|
|
719
|
+
}
|
|
720
|
+
return safeCopy;
|
|
721
|
+
}),
|
|
722
|
+
});
|
|
723
|
+
return new parentEntityClass(entity);
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
return normalizedParentEntities;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Register an inclusion resolver for the related model name.
|
|
730
|
+
*
|
|
731
|
+
* @param relationName - Name of the relation defined on the source model
|
|
732
|
+
* @param resolver - Resolver function for getting related model entities
|
|
733
|
+
*/
|
|
734
|
+
registerInclusionResolver(relationName, resolver) {
|
|
735
|
+
this.inclusionResolvers.set(relationName, resolver);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Function to create a constrained relation repository factory
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```ts
|
|
742
|
+
* class CustomerRepository extends SequelizeCrudRepository<
|
|
743
|
+
* Customer,
|
|
744
|
+
* typeof Customer.prototype.id,
|
|
745
|
+
* CustomerRelations
|
|
746
|
+
* > {
|
|
747
|
+
* public readonly orders: HasManyRepositoryFactory<Order, typeof Customer.prototype.id>;
|
|
748
|
+
*
|
|
749
|
+
* constructor(
|
|
750
|
+
* protected db: SequelizeDataSource,
|
|
751
|
+
* orderRepository: EntityCrudRepository<Order, typeof Order.prototype.id>,
|
|
752
|
+
* ) {
|
|
753
|
+
* super(Customer, db);
|
|
754
|
+
* this.orders = this.createHasManyRepositoryFactoryFor(
|
|
755
|
+
* 'orders',
|
|
756
|
+
* orderRepository,
|
|
757
|
+
* );
|
|
758
|
+
* }
|
|
759
|
+
* }
|
|
760
|
+
* ```
|
|
761
|
+
*
|
|
762
|
+
* @param relationName - Name of the relation defined on the source model
|
|
763
|
+
* @param targetRepo - Target repository instance
|
|
764
|
+
*/
|
|
765
|
+
createHasManyRepositoryFactoryFor(relationName, targetRepositoryGetter) {
|
|
766
|
+
const meta = this.entityClass.definition.relations[relationName];
|
|
767
|
+
return (0, repository_1.createHasManyRepositoryFactory)(meta, targetRepositoryGetter);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Function to create a belongs to accessor
|
|
771
|
+
*
|
|
772
|
+
* @param relationName - Name of the relation defined on the source model
|
|
773
|
+
* @param targetRepo - Target repository instance
|
|
774
|
+
*/
|
|
775
|
+
createBelongsToAccessorFor(relationName, targetRepositoryGetter) {
|
|
776
|
+
const meta = this.entityClass.definition.relations[relationName];
|
|
777
|
+
return (0, repository_1.createBelongsToAccessor)(meta, targetRepositoryGetter, this);
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Function to create a constrained hasOne relation repository factory
|
|
781
|
+
*
|
|
782
|
+
* @param relationName - Name of the relation defined on the source model
|
|
783
|
+
* @param targetRepo - Target repository instance
|
|
784
|
+
*/
|
|
785
|
+
createHasOneRepositoryFactoryFor(relationName, targetRepositoryGetter) {
|
|
786
|
+
const meta = this.entityClass.definition.relations[relationName];
|
|
787
|
+
return (0, repository_1.createHasOneRepositoryFactory)(meta, targetRepositoryGetter);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Function to create a constrained hasManyThrough relation repository factory
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* ```ts
|
|
794
|
+
* class CustomerRepository extends SequelizeCrudRepository<
|
|
795
|
+
* Customer,
|
|
796
|
+
* typeof Customer.prototype.id,
|
|
797
|
+
* CustomerRelations
|
|
798
|
+
* > {
|
|
799
|
+
* public readonly cartItems: HasManyRepositoryFactory<CartItem, typeof Customer.prototype.id>;
|
|
800
|
+
*
|
|
801
|
+
* constructor(
|
|
802
|
+
* protected db: SequelizeDataSource,
|
|
803
|
+
* cartItemRepository: EntityCrudRepository<CartItem, typeof, CartItem.prototype.id>,
|
|
804
|
+
* throughRepository: EntityCrudRepository<Through, typeof Through.prototype.id>,
|
|
805
|
+
* ) {
|
|
806
|
+
* super(Customer, db);
|
|
807
|
+
* this.cartItems = this.createHasManyThroughRepositoryFactoryFor(
|
|
808
|
+
* 'cartItems',
|
|
809
|
+
* cartItemRepository,
|
|
810
|
+
* );
|
|
811
|
+
* }
|
|
812
|
+
* }
|
|
813
|
+
* ```
|
|
814
|
+
*
|
|
815
|
+
* @param relationName - Name of the relation defined on the source model
|
|
816
|
+
* @param targetRepo - Target repository instance
|
|
817
|
+
* @param throughRepo - Through repository instance
|
|
818
|
+
*/
|
|
819
|
+
createHasManyThroughRepositoryFactoryFor(relationName, targetRepositoryGetter, throughRepositoryGetter) {
|
|
820
|
+
const meta = this.entityClass.definition.relations[relationName];
|
|
821
|
+
return (0, repository_1.createHasManyThroughRepositoryFactory)(meta, targetRepositoryGetter, throughRepositoryGetter);
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Function to create a references many accessor
|
|
825
|
+
*
|
|
826
|
+
* @param relationName - Name of the relation defined on the source model
|
|
827
|
+
* @param targetRepo - Target repository instance
|
|
828
|
+
*/
|
|
829
|
+
createReferencesManyAccessorFor(relationName, targetRepoGetter) {
|
|
830
|
+
const meta = this.entityClass.definition.relations[relationName];
|
|
831
|
+
return (0, repository_1.createReferencesManyAccessor)(meta, targetRepoGetter, this);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
exports.SequelizeCrudRepository = SequelizeCrudRepository;
|
|
835
|
+
//# sourceMappingURL=sequelize.repository.base.js.map
|