@loopback/sequelize 0.3.0 → 0.5.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 (63) hide show
  1. package/README.md +28 -2
  2. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/book-category.controller.js +2 -3
  3. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/book.controller.js +2 -3
  4. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/category.controller.js +2 -3
  5. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/developer.controller.js +2 -3
  6. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/doctor-patient.controller.js +2 -3
  7. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/doctor.controller.js +2 -3
  8. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/index.js +1 -0
  9. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/patient.controller.js +2 -3
  10. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/product.controller.js +2 -3
  11. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/programming-languange.controller.js +2 -3
  12. package/dist/.sandbox/40627f10KYa/controllers/task.controller.js +209 -0
  13. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/todo-list-todo.controller.js +2 -3
  14. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/todo-list.controller.js +3 -4
  15. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/todo-todo-list.controller.js +2 -3
  16. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/todo.controller.js +2 -3
  17. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/transaction.controller.js +2 -3
  18. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/user-todo-list.controller.js +2 -3
  19. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/user.controller.js +2 -3
  20. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/datasources/config.js +22 -0
  21. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/datasources/primary.datasource.js +2 -3
  22. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/datasources/secondary.datasource.js +2 -3
  23. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/appointment.model.js +2 -3
  24. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/book.model.js +2 -3
  25. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/category.model.js +2 -3
  26. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/developer.model.js +9 -3
  27. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/doctor.model.js +2 -3
  28. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/index.js +1 -0
  29. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/patient.model.js +2 -3
  30. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/product.model.js +2 -3
  31. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/programming-language.model.js +9 -3
  32. package/dist/.sandbox/40627f10KYa/models/task.model.js +73 -0
  33. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/todo-list.model.js +2 -3
  34. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/todo.model.js +2 -3
  35. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/models/user.model.js +11 -6
  36. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/appointment.repository.js +2 -3
  37. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/book.repository.js +2 -3
  38. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/category.repository.js +2 -3
  39. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/developer.repository.js +2 -3
  40. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/doctor.repository.js +2 -3
  41. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/index.js +1 -0
  42. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/patient.repository.js +2 -3
  43. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/product.repository.js +2 -3
  44. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/programming-language.repository.js +2 -3
  45. package/dist/.sandbox/40627f10KYa/repositories/task.repository.js +25 -0
  46. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/todo-list.repository.js +2 -3
  47. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/todo.repository.js +2 -3
  48. package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/repositories/user.repository.js +2 -3
  49. package/dist/component.js +2 -3
  50. package/dist/component.js.map +1 -1
  51. package/dist/keys.js +1 -1
  52. package/dist/keys.js.map +1 -1
  53. package/dist/sequelize/sequelize.datasource.base.d.ts +54 -3
  54. package/dist/sequelize/sequelize.datasource.base.js +75 -2
  55. package/dist/sequelize/sequelize.datasource.base.js.map +1 -1
  56. package/dist/sequelize/sequelize.repository.base.d.ts +70 -3
  57. package/dist/sequelize/sequelize.repository.base.js +132 -37
  58. package/dist/sequelize/sequelize.repository.base.js.map +1 -1
  59. package/package.json +14 -14
  60. package/src/sequelize/sequelize.datasource.base.ts +121 -4
  61. package/src/sequelize/sequelize.repository.base.ts +178 -64
  62. /package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/application.js +0 -0
  63. /package/dist/.sandbox/{61612HwhooZ → 40627f10KYa}/controllers/test.controller.base.js +0 -0
@@ -7,12 +7,8 @@ import {
7
7
  AnyObject,
8
8
  BelongsToAccessor,
9
9
  BelongsToDefinition,
10
+ Command,
10
11
  Count,
11
- createBelongsToAccessor,
12
- createHasManyRepositoryFactory,
13
- createHasManyThroughRepositoryFactory,
14
- createHasOneRepositoryFactory,
15
- createReferencesManyAccessor,
16
12
  DataObject,
17
13
  Entity,
18
14
  EntityCrudRepository,
@@ -29,14 +25,22 @@ import {
29
25
  Inclusion,
30
26
  InclusionFilter,
31
27
  InclusionResolver,
28
+ RelationType as LoopbackRelationType,
29
+ NamedParameters,
30
+ Options,
32
31
  PositionalParameters,
33
32
  PropertyDefinition,
34
33
  ReferencesManyAccessor,
35
34
  ReferencesManyDefinition,
36
- RelationType as LoopbackRelationType,
37
35
  Where,
36
+ createBelongsToAccessor,
37
+ createHasManyRepositoryFactory,
38
+ createHasManyThroughRepositoryFactory,
39
+ createHasOneRepositoryFactory,
40
+ createReferencesManyAccessor,
38
41
  } from '@loopback/repository';
39
42
  import debugFactory from 'debug';
43
+ import {nanoid} from 'nanoid';
40
44
  import {
41
45
  Attributes,
42
46
  DataType,
@@ -110,6 +114,29 @@ export class SequelizeCrudRepository<
110
114
  'sqlite3',
111
115
  ] as const;
112
116
 
117
+ /**
118
+ * Length of the `nanoid` generated for defaultFn's `shortid` and `nanoid` aliases.
119
+ */
120
+ public NANO_ID_LENGTH = 9;
121
+
122
+ /**
123
+ * The alias registry for `defaultFn` option used in model property definition.
124
+ *
125
+ * See: https://loopback.io/doc/en/lb4/Model.html#property-decorator
126
+ */
127
+ protected defaultFnRegistry: Record<string, unknown> = {
128
+ guid: DataTypes.UUIDV1,
129
+ uuid: DataTypes.UUIDV1,
130
+ uuidv4: DataTypes.UUIDV4,
131
+ now: DataTypes.NOW,
132
+ shortid: () => nanoid(this.NANO_ID_LENGTH),
133
+ nanoid: () => nanoid(this.NANO_ID_LENGTH),
134
+ };
135
+
136
+ protected getDefaultFnRegistry() {
137
+ return this.defaultFnRegistry;
138
+ }
139
+
113
140
  public readonly inclusionResolvers: Map<
114
141
  string,
115
142
  InclusionResolver<T, Entity>
@@ -121,17 +148,11 @@ export class SequelizeCrudRepository<
121
148
  public sequelizeModel: ModelStatic<Model<T>>;
122
149
 
123
150
  async create(entity: DataObject<T>, options?: AnyObject): Promise<T> {
124
- let err = null;
125
- const data = await this.sequelizeModel
126
- .create(entity as MakeNullishOptional<T>, options)
127
- .catch(error => {
128
- err = error;
129
- });
130
-
131
- if (!data) {
132
- throw new Error(err ?? 'Something went wrong');
133
- }
134
- return new this.entityClass(this.excludeHiddenProps(data.toJSON())) as T;
151
+ const data = await this.sequelizeModel.create(
152
+ entity as MakeNullishOptional<T>,
153
+ options,
154
+ );
155
+ return new this.entityClass(data.toJSON()) as T;
135
156
  }
136
157
 
137
158
  async createAll(
@@ -213,20 +234,15 @@ export class SequelizeCrudRepository<
213
234
  filter?: Filter<T>,
214
235
  options?: AnyObject,
215
236
  ): Promise<(T & Relations)[]> {
216
- const data = await this.sequelizeModel
217
- .findAll({
218
- include: this.buildSequelizeIncludeFilter(filter?.include),
219
- where: this.buildSequelizeWhere(filter?.where),
220
- attributes: this.buildSequelizeAttributeFilter(filter?.fields),
221
- order: this.buildSequelizeOrder(filter?.order),
222
- limit: filter?.limit,
223
- offset: filter?.offset ?? filter?.skip,
224
- ...options,
225
- })
226
- .catch(err => {
227
- debug('findAll() error:', err);
228
- throw new Error(err);
229
- });
237
+ const data = await this.sequelizeModel.findAll({
238
+ include: this.buildSequelizeIncludeFilter(filter?.include),
239
+ where: this.buildSequelizeWhere(filter?.where),
240
+ attributes: this.buildSequelizeAttributeFilter(filter?.fields),
241
+ order: this.buildSequelizeOrder(filter?.order),
242
+ limit: filter?.limit,
243
+ offset: filter?.offset ?? filter?.skip,
244
+ ...options,
245
+ });
230
246
 
231
247
  return this.includeReferencesIfRequested(
232
248
  data,
@@ -239,19 +255,14 @@ export class SequelizeCrudRepository<
239
255
  filter?: Filter<T>,
240
256
  options?: AnyObject,
241
257
  ): Promise<(T & Relations) | null> {
242
- const data = await this.sequelizeModel
243
- .findOne({
244
- include: this.buildSequelizeIncludeFilter(filter?.include),
245
- where: this.buildSequelizeWhere(filter?.where),
246
- attributes: this.buildSequelizeAttributeFilter(filter?.fields),
247
- order: this.buildSequelizeOrder(filter?.order),
248
- offset: filter?.offset ?? filter?.skip,
249
- ...options,
250
- })
251
- .catch(err => {
252
- debug('findOne() error:', err);
253
- throw new Error(err);
254
- });
258
+ const data = await this.sequelizeModel.findOne({
259
+ include: this.buildSequelizeIncludeFilter(filter?.include),
260
+ where: this.buildSequelizeWhere(filter?.where),
261
+ attributes: this.buildSequelizeAttributeFilter(filter?.fields),
262
+ order: this.buildSequelizeOrder(filter?.order),
263
+ offset: filter?.offset ?? filter?.skip,
264
+ ...options,
265
+ });
255
266
 
256
267
  if (data === null) {
257
268
  return Promise.resolve(null);
@@ -347,10 +358,41 @@ export class SequelizeCrudRepository<
347
358
  return {count};
348
359
  }
349
360
 
350
- async execute(..._args: PositionalParameters): Promise<AnyObject> {
351
- throw new Error(
352
- 'RAW Query execution is currently NOT supported for Sequelize CRUD Repository.',
353
- );
361
+ /**
362
+ * Execute a SQL command.
363
+ *
364
+ * **WARNING:** In general, it is always better to perform database actions
365
+ * through repository methods. Directly executing SQL may lead to unexpected
366
+ * results, corrupted data, security vulnerabilities and other issues.
367
+ *
368
+ * @example
369
+ *
370
+ * ```ts
371
+ * // MySQL
372
+ * const result = await repo.execute(
373
+ * 'SELECT * FROM Products WHERE size > ?',
374
+ * [42]
375
+ * );
376
+ *
377
+ * // PostgreSQL
378
+ * const result = await repo.execute(
379
+ * 'SELECT * FROM Products WHERE size > $1',
380
+ * [42]
381
+ * );
382
+ * ```
383
+ *
384
+ * @param command A parameterized SQL command or query.
385
+ * @param parameters List of parameter values to use.
386
+ * @param options Additional options, for example `transaction`.
387
+ * @returns A promise which resolves to the command output. The output type (data structure) is database specific and
388
+ * often depends on the command executed.
389
+ */
390
+ async execute(
391
+ command: Command,
392
+ parameters?: NamedParameters | PositionalParameters,
393
+ options?: Options,
394
+ ): Promise<AnyObject> {
395
+ return this.dataSource.execute(command, parameters, options);
354
396
  }
355
397
 
356
398
  protected toEntities(models: Model<T, T>[]): T[] {
@@ -442,6 +484,22 @@ export class SequelizeCrudRepository<
442
484
  });
443
485
  }
444
486
 
487
+ /**
488
+ * Checks if the resolver of the inclusion relation is registered
489
+ * in the inclusionResolver of the current repository
490
+ *
491
+ * @param include - LoopBack Inclusion filter
492
+ */
493
+ protected isInclusionAllowed(include: InclusionFilter): boolean {
494
+ const relationName =
495
+ typeof include === 'string' ? include : include.relation;
496
+ if (!relationName) {
497
+ return false;
498
+ }
499
+ const allowed = this.inclusionResolvers.has(relationName);
500
+ return allowed;
501
+ }
502
+
445
503
  /**
446
504
  * Build Sequelize compatible `include` filter
447
505
  * @param inclusionFilters - loopback style `where` condition
@@ -460,6 +518,25 @@ export class SequelizeCrudRepository<
460
518
  sourceModel = this.sequelizeModel;
461
519
  }
462
520
 
521
+ if (sourceModel === this.sequelizeModel) {
522
+ const invalidInclusions = inclusionFilters.filter(
523
+ inclusionFilter => !this.isInclusionAllowed(inclusionFilter),
524
+ );
525
+ if (invalidInclusions.length) {
526
+ const msg =
527
+ 'Invalid "filter.include" entries: ' +
528
+ invalidInclusions
529
+ .map(inclusionFilter => JSON.stringify(inclusionFilter))
530
+ .join('; ');
531
+ const err = new Error(msg);
532
+ Object.assign(err, {
533
+ code: 'INVALID_INCLUSION_FILTER',
534
+ statusCode: 400,
535
+ });
536
+ throw err;
537
+ }
538
+ }
539
+
463
540
  const includable: Includeable[] = [];
464
541
 
465
542
  for (const filter of inclusionFilters) {
@@ -603,7 +680,7 @@ export class SequelizeCrudRepository<
603
680
  this.getSequelizeModelAttributes(entityClass.definition.properties),
604
681
  {
605
682
  timestamps: false,
606
- tableName: entityClass.modelName.toLowerCase(),
683
+ tableName: this.getTableName(entityClass),
607
684
  freezeTableName: true,
608
685
  },
609
686
  );
@@ -683,6 +760,32 @@ export class SequelizeCrudRepository<
683
760
  return sourceModel;
684
761
  }
685
762
 
763
+ /**
764
+ * This function retrieves the table name associated with a given entity class.
765
+ * Different loopback connectors have different conventions for picking up table names,
766
+ * unless the name is specified in the @model decorator.
767
+ *
768
+ * The function follows the following cases to determine the table name:
769
+ * - It checks if the name property is specified in the @model decorator and uses it. (this takes precedence over all other cases)
770
+ * - If the dialect of the dataSource is PostgreSQL, it uses the lowercased version of the model class name.
771
+ * - If the dialect is MySQL or any other dialect, it uses the default model class name.
772
+ * @param {Entity} entityClass - The entity class for which the table name is being retrieved.
773
+ * @returns {string} - The table name associated with the entity class. Which is used when performing the query.
774
+ */
775
+ getTableName(entityClass = this.entityClass) {
776
+ let tableName = entityClass.name; // model class name
777
+
778
+ if (entityClass.definition.name !== tableName) {
779
+ // name is specified in decorator
780
+ tableName = entityClass.definition.name;
781
+ } else if (this.dataSource.sequelizeConfig.dialect === 'postgres') {
782
+ // postgres is being used and name is not specified in @model decorator
783
+ tableName = entityClass.modelName.toLowerCase();
784
+ }
785
+
786
+ return tableName;
787
+ }
788
+
686
789
  /**
687
790
  * Run CREATE TABLE query for the target sequelize model, Useful for quick testing
688
791
  * @param options Sequelize Sync Options
@@ -793,11 +896,18 @@ export class SequelizeCrudRepository<
793
896
  );
794
897
  }
795
898
 
899
+ let defaultValue = definition[propName].default;
900
+ const originalDefaultFn = definition[propName]['defaultFn'];
901
+
902
+ if (typeof originalDefaultFn === 'function') {
903
+ defaultValue = originalDefaultFn;
904
+ } else if (originalDefaultFn in this.getDefaultFnRegistry()) {
905
+ defaultValue = this.getDefaultFnRegistry()[originalDefaultFn];
906
+ }
907
+
796
908
  const columnOptions: ModelAttributeColumnOptions = {
797
909
  type: dataType,
798
- ...('default' in definition[propName]
799
- ? {defaultValue: definition[propName].default}
800
- : {}),
910
+ defaultValue,
801
911
  };
802
912
 
803
913
  // Set column as `primaryKey` when id is set to true (which is loopback way to define primary key)
@@ -829,6 +939,11 @@ export class SequelizeCrudRepository<
829
939
  * Remove hidden properties specified in model from response body. (See: https://github.com/sourcefuse/loopback4-sequelize/issues/3)
830
940
  * @param entity normalized entity. You can use `entity.toJSON()`'s value
831
941
  * @returns normalized entity excluding the hiddenProperties
942
+ *
943
+ * @deprecated To exclude hidden props from an entity instance, call the `.toJSON()` method on it.
944
+ * Alternatively it can be use by manually instantiating the model using `new EntityClass(data).toJSON()`.
945
+ *
946
+ * This function will be removed in next major release.
832
947
  */
833
948
  protected excludeHiddenProps(entity: T & Relations): T & Relations {
834
949
  const hiddenProps = this.entityClass.definition.settings.hiddenProperties;
@@ -862,6 +977,9 @@ export class SequelizeCrudRepository<
862
977
  if (!parentEntityClass) {
863
978
  parentEntityClass = this.entityClass;
864
979
  }
980
+ let parentEntityInstances = parentEntities.map(
981
+ sequelizeModel => new this.entityClass(sequelizeModel.toJSON()) as T,
982
+ );
865
983
  /**
866
984
  * All columns names defined in model with `@referencesMany`
867
985
  */
@@ -882,8 +1000,8 @@ export class SequelizeCrudRepository<
882
1000
 
883
1001
  // Validate data type of items in any column having references
884
1002
  // For eg. convert ["1", "2"] into [1, 2] if `itemType` specified is `number[]`
885
- const normalizedParentEntities = parentEntities.map(entity => {
886
- const data = entity.toJSON();
1003
+ parentEntityInstances = parentEntityInstances.map(entity => {
1004
+ const data = entity as AnyObject;
887
1005
  for (const columnName in data) {
888
1006
  if (!allReferencesColumns.includes(columnName)) {
889
1007
  // Column is not the one used for referencesMany relation. Eg. "programmingLanguageIds"
@@ -915,7 +1033,7 @@ export class SequelizeCrudRepository<
915
1033
  data[columnName] = items as unknown as T[Extract<keyof T, string>];
916
1034
  }
917
1035
 
918
- return data;
1036
+ return data as T;
919
1037
  });
920
1038
 
921
1039
  // Requested inclusions of referencesMany relation
@@ -951,14 +1069,11 @@ export class SequelizeCrudRepository<
951
1069
  }
952
1070
 
953
1071
  if (referencesManyInclusions.length === 0) {
954
- const entityClasses = normalizedParentEntities.map(
955
- e => new parentEntityClass(e),
956
- );
957
- return entityClasses as (T & Relations)[];
1072
+ return parentEntityInstances as (T & Relations)[];
958
1073
  }
959
1074
 
960
1075
  for (const relation of referencesManyInclusions) {
961
- normalizedParentEntities.forEach(entity => {
1076
+ parentEntityInstances.forEach(entity => {
962
1077
  if (!relation.definition.keyFrom) {
963
1078
  return;
964
1079
  }
@@ -1038,7 +1153,7 @@ export class SequelizeCrudRepository<
1038
1153
  relation.filter.scope?.include,
1039
1154
  );
1040
1155
 
1041
- normalizedParentEntities.forEach(entity => {
1156
+ parentEntityInstances.forEach(entity => {
1042
1157
  const foreignKeys = entity[relation.definition.keyFrom as keyof T];
1043
1158
  const filteredChildModels = childModelData.filter(childModel => {
1044
1159
  if (Array.isArray(foreignKeys)) {
@@ -1056,15 +1171,14 @@ export class SequelizeCrudRepository<
1056
1171
  if (includeForeignKeyInResponse === false) {
1057
1172
  delete safeCopy[foreignKey as keyof typeof safeCopy];
1058
1173
  }
1059
- return safeCopy;
1174
+ return new targetLoopbackModel(safeCopy);
1060
1175
  },
1061
1176
  ),
1062
1177
  });
1063
- return new parentEntityClass(entity) as T & Relations;
1064
1178
  });
1065
1179
  }
1066
1180
 
1067
- return normalizedParentEntities as (T & Relations)[];
1181
+ return parentEntityInstances as (T & Relations)[];
1068
1182
  }
1069
1183
 
1070
1184
  /**