@naturalcycles/db-lib 9.8.0 → 9.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -149,7 +149,8 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
149
149
  * Does NOT mutate the object.
150
150
  * Validates (unless `skipValidation=true` passed).
151
151
  */
152
- validateAndConvert<T>(obj: Partial<T>, schema: ObjectSchema<T> | AjvSchema<T> | ZodSchema<T> | undefined, opt?: CommonDaoOptions): any;
152
+ validateAndConvert<T>(obj: Partial<T>, schema: ObjectSchema<T> | AjvSchema<T> | ZodSchema<T> | undefined, op?: 'load' | 'save', // this is to skip validation if validateOnLoad/Save is false
153
+ opt?: CommonDaoOptions): any;
153
154
  getTableSchema(): Promise<JsonSchemaRootObject<DBM>>;
154
155
  createTable(schema: JsonSchemaObject<DBM>, opt?: CommonDaoCreateOptions): Promise<void>;
155
156
  /**
@@ -27,6 +27,8 @@ class CommonDao {
27
27
  assignGeneratedIds: false,
28
28
  useCreatedProperty: true,
29
29
  useUpdatedProperty: true,
30
+ validateOnLoad: true,
31
+ validateOnSave: true,
30
32
  logger: console,
31
33
  ...cfg,
32
34
  hooks: {
@@ -49,7 +51,7 @@ class CommonDao {
49
51
  const bm = this.cfg.hooks.beforeCreate(part);
50
52
  // First assignIdCreatedUpdated, then validate!
51
53
  this.assignIdCreatedUpdated(bm, opt);
52
- return this.validateAndConvert(bm, this.cfg.bmSchema, opt);
54
+ return this.validateAndConvert(bm, this.cfg.bmSchema, undefined, opt);
53
55
  }
54
56
  async getById(id, opt = {}) {
55
57
  if (!id)
@@ -245,7 +247,6 @@ class CommonDao {
245
247
  async streamQueryForEach(q, mapper, opt = {}) {
246
248
  q.table = opt.table || q.table;
247
249
  opt.skipValidation = opt.skipValidation !== false; // default true
248
- opt.skipConversion = opt.skipConversion !== false; // default true
249
250
  opt.errorMode ||= js_lib_1.ErrorMode.SUPPRESS;
250
251
  const partialQuery = !!q._selectedFieldNames;
251
252
  const op = `streamQueryForEach(${q.pretty()})`;
@@ -284,7 +285,6 @@ class CommonDao {
284
285
  async streamQueryAsDBMForEach(q, mapper, opt = {}) {
285
286
  q.table = opt.table || q.table;
286
287
  opt.skipValidation = opt.skipValidation !== false; // default true
287
- opt.skipConversion = opt.skipConversion !== false; // default true
288
288
  opt.errorMode ||= js_lib_1.ErrorMode.SUPPRESS;
289
289
  const partialQuery = !!q._selectedFieldNames;
290
290
  const op = `streamQueryAsDBMForEach(${q.pretty()})`;
@@ -326,7 +326,6 @@ class CommonDao {
326
326
  streamQueryAsDBM(q, opt = {}) {
327
327
  q.table = opt.table || q.table;
328
328
  opt.skipValidation = opt.skipValidation !== false; // default true
329
- opt.skipConversion = opt.skipConversion !== false; // default true
330
329
  opt.errorMode ||= js_lib_1.ErrorMode.SUPPRESS;
331
330
  const partialQuery = !!q._selectedFieldNames;
332
331
  const stream = this.cfg.db.streamQuery(q, opt);
@@ -357,7 +356,6 @@ class CommonDao {
357
356
  streamQuery(q, opt = {}) {
358
357
  q.table = opt.table || q.table;
359
358
  opt.skipValidation = opt.skipValidation !== false; // default true
360
- opt.skipConversion = opt.skipConversion !== false; // default true
361
359
  opt.errorMode ||= js_lib_1.ErrorMode.SUPPRESS;
362
360
  const stream = this.cfg.db.streamQuery(q, opt);
363
361
  const partialQuery = !!q._selectedFieldNames;
@@ -496,23 +494,29 @@ class CommonDao {
496
494
  return await this.patchInTransaction(bm, patch, opt);
497
495
  }
498
496
  if (opt.skipDBRead) {
499
- const bmBefore = (0, js_lib_1._deepCopy)(bm);
500
- Object.assign(bm, patch);
501
- if ((0, js_lib_1._deepJsonEquals)(bm, bmBefore)) {
497
+ const patched = {
498
+ ...bm,
499
+ ...patch,
500
+ };
501
+ if ((0, js_lib_1._deepJsonEquals)(bm, patched)) {
502
502
  // Skipping the save operation, as data is the same
503
503
  return bm;
504
504
  }
505
+ Object.assign(bm, patch);
505
506
  }
506
507
  else {
507
508
  const loaded = await this.getById(bm.id, opt);
508
509
  if (loaded) {
509
- Object.assign(loaded, patch);
510
- if ((0, js_lib_1._deepJsonEquals)(loaded, bm)) {
510
+ const loadedWithPatch = {
511
+ ...loaded,
512
+ ...patch,
513
+ };
514
+ // Make `bm` exactly the same as `loadedWithPatch`
515
+ (0, js_lib_1._objectAssignExact)(bm, loadedWithPatch);
516
+ if ((0, js_lib_1._deepJsonEquals)(loaded, loadedWithPatch)) {
511
517
  // Skipping the save operation, as data is the same
512
518
  return bm;
513
519
  }
514
- // Make `bm` exactly the same as `loaded`
515
- (0, js_lib_1._objectAssignExact)(bm, loaded);
516
520
  }
517
521
  else {
518
522
  Object.assign(bm, patch);
@@ -537,7 +541,7 @@ class CommonDao {
537
541
  // We compare with convertedBM, to account for cases when some extra property is assigned to bm,
538
542
  // which should be removed post-validation, but it breaks the "equality check"
539
543
  // Post-validation the equality check should work as intended
540
- const convertedBM = this.validateAndConvert(bm, this.cfg.bmSchema, opt);
544
+ const convertedBM = this.validateAndConvert(bm, this.cfg.bmSchema, 'save', opt);
541
545
  if ((0, js_lib_1._deepJsonEquals)(convertedBM, opt.skipIfEquals)) {
542
546
  // Skipping the save operation
543
547
  return bm;
@@ -682,7 +686,6 @@ class CommonDao {
682
686
  this.requireWriteAccess();
683
687
  const table = opt.table || this.cfg.table;
684
688
  opt.skipValidation ??= true;
685
- opt.skipConversion ??= true;
686
689
  opt.errorMode ||= js_lib_1.ErrorMode.SUPPRESS;
687
690
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
688
691
  opt = { ...opt, saveMethod: 'insert' };
@@ -817,7 +820,7 @@ class CommonDao {
817
820
  // DBM > BM
818
821
  const bm = ((await this.cfg.hooks.beforeDBMToBM?.(dbm)) || dbm);
819
822
  // Validate/convert BM
820
- return this.validateAndConvert(bm, this.cfg.bmSchema, opt);
823
+ return this.validateAndConvert(bm, this.cfg.bmSchema, 'load', opt);
821
824
  }
822
825
  async dbmsToBM(dbms, opt = {}) {
823
826
  return await (0, js_lib_1.pMap)(dbms, async (dbm) => await this.dbmToBM(dbm, opt));
@@ -826,7 +829,7 @@ class CommonDao {
826
829
  if (bm === undefined)
827
830
  return;
828
831
  // bm gets assigned to the new reference
829
- bm = this.validateAndConvert(bm, this.cfg.bmSchema, opt);
832
+ bm = this.validateAndConvert(bm, this.cfg.bmSchema, 'save', opt);
830
833
  // BM > DBM
831
834
  return ((await this.cfg.hooks.beforeBMToDBM?.(bm)) || bm);
832
835
  }
@@ -857,7 +860,8 @@ class CommonDao {
857
860
  * Does NOT mutate the object.
858
861
  * Validates (unless `skipValidation=true` passed).
859
862
  */
860
- validateAndConvert(obj, schema, opt = {}) {
863
+ validateAndConvert(obj, schema, op, // this is to skip validation if validateOnLoad/Save is false
864
+ opt = {}) {
861
865
  // Kirill 2021-10-18: I realized that there's little reason to keep removing null values
862
866
  // So, from now on we'll preserve them
863
867
  // "undefined" values, I believe, are/were not saved to/from DB anyway (due to e.g JSON.stringify removing them)
@@ -869,7 +873,10 @@ class CommonDao {
869
873
  // and they can be annoying with snapshot tests
870
874
  obj = (0, js_lib_1._filterUndefinedValues)(obj);
871
875
  // Return as is if no schema is passed or if `skipConversion` is set
872
- if (!schema || opt.skipConversion) {
876
+ if (!schema ||
877
+ opt.skipValidation ||
878
+ (op === 'load' && !this.cfg.validateOnLoad) ||
879
+ (op === 'save' && !this.cfg.validateOnSave)) {
873
880
  return obj;
874
881
  }
875
882
  // This will Convert and Validate
@@ -897,7 +904,7 @@ class CommonDao {
897
904
  convertedValue = vr.value;
898
905
  }
899
906
  // If we care about validation and there's an error
900
- if (error && !opt.skipValidation) {
907
+ if (error) {
901
908
  const processedError = this.cfg.hooks.onValidationError(error);
902
909
  if (processedError)
903
910
  throw processedError;
@@ -101,6 +101,16 @@ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity
101
101
  */
102
102
  bmSchema?: ObjectSchema<BM> | AjvSchema<BM> | ZodSchema<BM>;
103
103
  excludeFromIndexes?: (keyof DBM)[];
104
+ /**
105
+ * Defaults to true.
106
+ * If set to false - load (read) operations will skip validation (and conversion).
107
+ */
108
+ validateOnLoad?: boolean;
109
+ /**
110
+ * Defaults to true.
111
+ * If set to false - save (write) operations will skip validation (and conversion).
112
+ */
113
+ validateOnSave?: boolean;
104
114
  /**
105
115
  * Defaults to false.
106
116
  * Setting it to true will set saveMethod to `insert` for save/saveBatch, which will
@@ -163,20 +173,12 @@ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity
163
173
  */
164
174
  export interface CommonDaoOptions extends CommonDBOptions {
165
175
  /**
166
- * If true - will ignore the validation result, but will STILL DO the validation step, which will DO conversion
167
- * (according to Joi schema).
168
- *
169
- * Set skipConversion=true (or raw=true) to bypass conversion step as well (e.g for performance reasons).
176
+ * Defaults to false.
170
177
  *
171
- * @default false
178
+ * If set to true - will disable validation (and conversion).
179
+ * One possible use case of doing this is - performance (as validation/conversion takes time, especially with Joi).
172
180
  */
173
181
  skipValidation?: boolean;
174
- /**
175
- * If true - will SKIP the joi validation AND conversion steps alltogether. To improve performance of DAO.
176
- *
177
- * @default false
178
- */
179
- skipConversion?: boolean;
180
182
  /**
181
183
  * @default false
182
184
  */
@@ -241,10 +243,6 @@ export interface CommonDaoStreamOptions<IN> extends CommonDaoOptions, TransformL
241
243
  * @default true (for streams)
242
244
  */
243
245
  skipValidation?: boolean;
244
- /**
245
- * @default true (for streams)
246
- */
247
- skipConversion?: boolean;
248
246
  /**
249
247
  * @default ErrorMode.SUPPRESS for returning ReadableStream, because .pipe() has no concept of "error propagation"
250
248
  * @default ErrorMode.SUPPRESS for .forEach() streams as well, but overridable
package/package.json CHANGED
@@ -40,7 +40,7 @@
40
40
  "engines": {
41
41
  "node": ">=18.12"
42
42
  },
43
- "version": "9.8.0",
43
+ "version": "9.9.1",
44
44
  "description": "Lowest Common Denominator API to supported Databases",
45
45
  "keywords": [
46
46
  "db",
@@ -129,6 +129,18 @@ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity
129
129
 
130
130
  excludeFromIndexes?: (keyof DBM)[]
131
131
 
132
+ /**
133
+ * Defaults to true.
134
+ * If set to false - load (read) operations will skip validation (and conversion).
135
+ */
136
+ validateOnLoad?: boolean
137
+
138
+ /**
139
+ * Defaults to true.
140
+ * If set to false - save (write) operations will skip validation (and conversion).
141
+ */
142
+ validateOnSave?: boolean
143
+
132
144
  /**
133
145
  * Defaults to false.
134
146
  * Setting it to true will set saveMethod to `insert` for save/saveBatch, which will
@@ -203,22 +215,13 @@ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity
203
215
  */
204
216
  export interface CommonDaoOptions extends CommonDBOptions {
205
217
  /**
206
- * If true - will ignore the validation result, but will STILL DO the validation step, which will DO conversion
207
- * (according to Joi schema).
208
- *
209
- * Set skipConversion=true (or raw=true) to bypass conversion step as well (e.g for performance reasons).
218
+ * Defaults to false.
210
219
  *
211
- * @default false
220
+ * If set to true - will disable validation (and conversion).
221
+ * One possible use case of doing this is - performance (as validation/conversion takes time, especially with Joi).
212
222
  */
213
223
  skipValidation?: boolean
214
224
 
215
- /**
216
- * If true - will SKIP the joi validation AND conversion steps alltogether. To improve performance of DAO.
217
- *
218
- * @default false
219
- */
220
- skipConversion?: boolean
221
-
222
225
  /**
223
226
  * @default false
224
227
  */
@@ -302,11 +305,6 @@ export interface CommonDaoStreamOptions<IN>
302
305
  */
303
306
  skipValidation?: boolean
304
307
 
305
- /**
306
- * @default true (for streams)
307
- */
308
- skipConversion?: boolean
309
-
310
308
  /**
311
309
  * @default ErrorMode.SUPPRESS for returning ReadableStream, because .pipe() has no concept of "error propagation"
312
310
  * @default ErrorMode.SUPPRESS for .forEach() streams as well, but overridable
@@ -81,6 +81,8 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
81
81
  assignGeneratedIds: false,
82
82
  useCreatedProperty: true,
83
83
  useUpdatedProperty: true,
84
+ validateOnLoad: true,
85
+ validateOnSave: true,
84
86
  logger: console,
85
87
  ...cfg,
86
88
  hooks: {
@@ -104,7 +106,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
104
106
  const bm = this.cfg.hooks!.beforeCreate!(part)
105
107
  // First assignIdCreatedUpdated, then validate!
106
108
  this.assignIdCreatedUpdated(bm, opt)
107
- return this.validateAndConvert(bm, this.cfg.bmSchema, opt)
109
+ return this.validateAndConvert(bm, this.cfg.bmSchema, undefined, opt)
108
110
  }
109
111
 
110
112
  // GET
@@ -353,7 +355,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
353
355
  ): Promise<void> {
354
356
  q.table = opt.table || q.table
355
357
  opt.skipValidation = opt.skipValidation !== false // default true
356
- opt.skipConversion = opt.skipConversion !== false // default true
357
358
  opt.errorMode ||= ErrorMode.SUPPRESS
358
359
 
359
360
  const partialQuery = !!q._selectedFieldNames
@@ -403,7 +404,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
403
404
  ): Promise<void> {
404
405
  q.table = opt.table || q.table
405
406
  opt.skipValidation = opt.skipValidation !== false // default true
406
- opt.skipConversion = opt.skipConversion !== false // default true
407
407
  opt.errorMode ||= ErrorMode.SUPPRESS
408
408
 
409
409
  const partialQuery = !!q._selectedFieldNames
@@ -452,7 +452,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
452
452
  streamQueryAsDBM(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<DBM> = {}): ReadableTyped<DBM> {
453
453
  q.table = opt.table || q.table
454
454
  opt.skipValidation = opt.skipValidation !== false // default true
455
- opt.skipConversion = opt.skipConversion !== false // default true
456
455
  opt.errorMode ||= ErrorMode.SUPPRESS
457
456
 
458
457
  const partialQuery = !!q._selectedFieldNames
@@ -491,7 +490,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
491
490
  streamQuery(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<BM> = {}): ReadableTyped<BM> {
492
491
  q.table = opt.table || q.table
493
492
  opt.skipValidation = opt.skipValidation !== false // default true
494
- opt.skipConversion = opt.skipConversion !== false // default true
495
493
  opt.errorMode ||= ErrorMode.SUPPRESS
496
494
 
497
495
  const stream = this.cfg.db.streamQuery<DBM>(q, opt)
@@ -671,25 +669,32 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
671
669
  }
672
670
 
673
671
  if (opt.skipDBRead) {
674
- const bmBefore = _deepCopy(bm)
675
- Object.assign(bm, patch)
676
- if (_deepJsonEquals(bm, bmBefore)) {
672
+ const patched: BM = {
673
+ ...bm,
674
+ ...patch,
675
+ }
676
+
677
+ if (_deepJsonEquals(bm, patched)) {
677
678
  // Skipping the save operation, as data is the same
678
679
  return bm
679
680
  }
681
+ Object.assign(bm, patch)
680
682
  } else {
681
683
  const loaded = await this.getById(bm.id, opt)
682
684
 
683
685
  if (loaded) {
684
- Object.assign(loaded, patch)
686
+ const loadedWithPatch: BM = {
687
+ ...loaded,
688
+ ...patch,
689
+ }
690
+
691
+ // Make `bm` exactly the same as `loadedWithPatch`
692
+ _objectAssignExact(bm, loadedWithPatch)
685
693
 
686
- if (_deepJsonEquals(loaded, bm)) {
694
+ if (_deepJsonEquals(loaded, loadedWithPatch)) {
687
695
  // Skipping the save operation, as data is the same
688
696
  return bm
689
697
  }
690
-
691
- // Make `bm` exactly the same as `loaded`
692
- _objectAssignExact(bm, loaded)
693
698
  } else {
694
699
  Object.assign(bm, patch)
695
700
  }
@@ -721,7 +726,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
721
726
  // We compare with convertedBM, to account for cases when some extra property is assigned to bm,
722
727
  // which should be removed post-validation, but it breaks the "equality check"
723
728
  // Post-validation the equality check should work as intended
724
- const convertedBM = this.validateAndConvert(bm as Partial<BM>, this.cfg.bmSchema, opt)
729
+ const convertedBM = this.validateAndConvert(bm as Partial<BM>, this.cfg.bmSchema, 'save', opt)
725
730
  if (_deepJsonEquals(convertedBM, opt.skipIfEquals)) {
726
731
  // Skipping the save operation
727
732
  return bm as BM
@@ -900,7 +905,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
900
905
 
901
906
  const table = opt.table || this.cfg.table
902
907
  opt.skipValidation ??= true
903
- opt.skipConversion ??= true
904
908
  opt.errorMode ||= ErrorMode.SUPPRESS
905
909
 
906
910
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
@@ -1077,7 +1081,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1077
1081
  const bm = ((await this.cfg.hooks!.beforeDBMToBM?.(dbm)) || dbm) as Partial<BM>
1078
1082
 
1079
1083
  // Validate/convert BM
1080
- return this.validateAndConvert(bm, this.cfg.bmSchema, opt)
1084
+ return this.validateAndConvert(bm, this.cfg.bmSchema, 'load', opt)
1081
1085
  }
1082
1086
 
1083
1087
  async dbmsToBM(dbms: DBM[], opt: CommonDaoOptions = {}): Promise<BM[]> {
@@ -1094,7 +1098,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1094
1098
  if (bm === undefined) return
1095
1099
 
1096
1100
  // bm gets assigned to the new reference
1097
- bm = this.validateAndConvert(bm, this.cfg.bmSchema, opt)
1101
+ bm = this.validateAndConvert(bm, this.cfg.bmSchema, 'save', opt)
1098
1102
 
1099
1103
  // BM > DBM
1100
1104
  return ((await this.cfg.hooks!.beforeBMToDBM?.(bm!)) || bm) as DBM
@@ -1138,6 +1142,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1138
1142
  validateAndConvert<T>(
1139
1143
  obj: Partial<T>,
1140
1144
  schema: ObjectSchema<T> | AjvSchema<T> | ZodSchema<T> | undefined,
1145
+ op?: 'load' | 'save', // this is to skip validation if validateOnLoad/Save is false
1141
1146
  opt: CommonDaoOptions = {},
1142
1147
  ): any {
1143
1148
  // Kirill 2021-10-18: I realized that there's little reason to keep removing null values
@@ -1152,7 +1157,12 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1152
1157
  obj = _filterUndefinedValues(obj)
1153
1158
 
1154
1159
  // Return as is if no schema is passed or if `skipConversion` is set
1155
- if (!schema || opt.skipConversion) {
1160
+ if (
1161
+ !schema ||
1162
+ opt.skipValidation ||
1163
+ (op === 'load' && !this.cfg.validateOnLoad) ||
1164
+ (op === 'save' && !this.cfg.validateOnSave)
1165
+ ) {
1156
1166
  return obj
1157
1167
  }
1158
1168
 
@@ -1183,7 +1193,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1183
1193
  }
1184
1194
 
1185
1195
  // If we care about validation and there's an error
1186
- if (error && !opt.skipValidation) {
1196
+ if (error) {
1187
1197
  const processedError = this.cfg.hooks!.onValidationError!(error)
1188
1198
 
1189
1199
  if (processedError) throw processedError