@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.
Files changed (87) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +194 -0
  3. package/dist/.sandbox/6646miobBk/application.js +15 -0
  4. package/dist/.sandbox/6646miobBk/controllers/book-category.controller.js +41 -0
  5. package/dist/.sandbox/6646miobBk/controllers/book.controller.js +210 -0
  6. package/dist/.sandbox/6646miobBk/controllers/category.controller.js +198 -0
  7. package/dist/.sandbox/6646miobBk/controllers/developer.controller.js +177 -0
  8. package/dist/.sandbox/6646miobBk/controllers/doctor-patient.controller.js +112 -0
  9. package/dist/.sandbox/6646miobBk/controllers/doctor.controller.js +177 -0
  10. package/dist/.sandbox/6646miobBk/controllers/index.js +20 -0
  11. package/dist/.sandbox/6646miobBk/controllers/patient.controller.js +165 -0
  12. package/dist/.sandbox/6646miobBk/controllers/programming-languange.controller.js +204 -0
  13. package/dist/.sandbox/6646miobBk/controllers/test.controller.base.js +25 -0
  14. package/dist/.sandbox/6646miobBk/controllers/todo-list-todo.controller.js +113 -0
  15. package/dist/.sandbox/6646miobBk/controllers/todo-list.controller.js +177 -0
  16. package/dist/.sandbox/6646miobBk/controllers/todo-todo-list.controller.js +41 -0
  17. package/dist/.sandbox/6646miobBk/controllers/todo.controller.js +177 -0
  18. package/dist/.sandbox/6646miobBk/controllers/user-todo-list.controller.js +113 -0
  19. package/dist/.sandbox/6646miobBk/controllers/user.controller.js +210 -0
  20. package/dist/.sandbox/6646miobBk/datasources/db.datasource.js +28 -0
  21. package/dist/.sandbox/6646miobBk/models/appointment.model.js +37 -0
  22. package/dist/.sandbox/6646miobBk/models/book.model.js +48 -0
  23. package/dist/.sandbox/6646miobBk/models/category.model.js +32 -0
  24. package/dist/.sandbox/6646miobBk/models/developer.model.js +40 -0
  25. package/dist/.sandbox/6646miobBk/models/doctor.model.js +44 -0
  26. package/dist/.sandbox/6646miobBk/models/index.js +15 -0
  27. package/dist/.sandbox/6646miobBk/models/patient.model.js +32 -0
  28. package/dist/.sandbox/6646miobBk/models/programming-language.model.js +32 -0
  29. package/dist/.sandbox/6646miobBk/models/todo-list.model.js +45 -0
  30. package/dist/.sandbox/6646miobBk/models/todo.model.js +44 -0
  31. package/dist/.sandbox/6646miobBk/models/user.model.js +85 -0
  32. package/dist/.sandbox/6646miobBk/repositories/appointment.repository.js +20 -0
  33. package/dist/.sandbox/6646miobBk/repositories/book.repository.js +25 -0
  34. package/dist/.sandbox/6646miobBk/repositories/category.repository.js +20 -0
  35. package/dist/.sandbox/6646miobBk/repositories/developer.repository.js +25 -0
  36. package/dist/.sandbox/6646miobBk/repositories/doctor.repository.js +27 -0
  37. package/dist/.sandbox/6646miobBk/repositories/index.js +15 -0
  38. package/dist/.sandbox/6646miobBk/repositories/patient.repository.js +20 -0
  39. package/dist/.sandbox/6646miobBk/repositories/programming-language.repository.js +20 -0
  40. package/dist/.sandbox/6646miobBk/repositories/todo-list.repository.js +25 -0
  41. package/dist/.sandbox/6646miobBk/repositories/todo.repository.js +25 -0
  42. package/dist/.sandbox/6646miobBk/repositories/user.repository.js +29 -0
  43. package/dist/component.d.ts +7 -0
  44. package/dist/component.js +30 -0
  45. package/dist/component.js.map +1 -0
  46. package/dist/index.d.ts +4 -0
  47. package/dist/index.js +12 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/keys.d.ts +8 -0
  50. package/dist/keys.js +16 -0
  51. package/dist/keys.js.map +1 -0
  52. package/dist/sequelize/connector-mapping.d.ts +9 -0
  53. package/dist/sequelize/connector-mapping.js +19 -0
  54. package/dist/sequelize/connector-mapping.js.map +1 -0
  55. package/dist/sequelize/index.d.ts +2 -0
  56. package/dist/sequelize/index.js +10 -0
  57. package/dist/sequelize/index.js.map +1 -0
  58. package/dist/sequelize/operator-translation.d.ts +8 -0
  59. package/dist/sequelize/operator-translation.js +31 -0
  60. package/dist/sequelize/operator-translation.js.map +1 -0
  61. package/dist/sequelize/sequelize.datasource.base.d.ts +23 -0
  62. package/dist/sequelize/sequelize.datasource.base.js +60 -0
  63. package/dist/sequelize/sequelize.datasource.base.js.map +1 -0
  64. package/dist/sequelize/sequelize.model.d.ts +7 -0
  65. package/dist/sequelize/sequelize.model.js +24 -0
  66. package/dist/sequelize/sequelize.model.js.map +1 -0
  67. package/dist/sequelize/sequelize.repository.base.d.ts +231 -0
  68. package/dist/sequelize/sequelize.repository.base.js +835 -0
  69. package/dist/sequelize/sequelize.repository.base.js.map +1 -0
  70. package/dist/sequelize/utils.d.ts +6 -0
  71. package/dist/sequelize/utils.js +17 -0
  72. package/dist/sequelize/utils.js.map +1 -0
  73. package/dist/types.d.ts +9 -0
  74. package/dist/types.js +14 -0
  75. package/dist/types.js.map +1 -0
  76. package/package.json +64 -0
  77. package/src/component.ts +34 -0
  78. package/src/index.ts +9 -0
  79. package/src/keys.ts +16 -0
  80. package/src/sequelize/connector-mapping.ts +26 -0
  81. package/src/sequelize/index.ts +7 -0
  82. package/src/sequelize/operator-translation.ts +32 -0
  83. package/src/sequelize/sequelize.datasource.base.ts +81 -0
  84. package/src/sequelize/sequelize.model.ts +22 -0
  85. package/src/sequelize/sequelize.repository.base.ts +1246 -0
  86. package/src/sequelize/utils.ts +13 -0
  87. 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