@naturalcycles/db-lib 9.7.2 → 9.9.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.
@@ -4,7 +4,7 @@ import { AsyncMapper, BaseDBEntity, CommonLogger, JsonSchemaObject, JsonSchemaRo
4
4
  import { AjvSchema, ObjectSchema, ReadableTyped } from '@naturalcycles/nodejs-lib';
5
5
  import { CommonDBTransactionOptions, DBPatch, DBTransaction, RunQueryResult } from '../db.model';
6
6
  import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
7
- import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model';
7
+ import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoPatchOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model';
8
8
  /**
9
9
  * Lowest common denominator API between supported Databases.
10
10
  *
@@ -78,15 +78,6 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
78
78
  * "Returns", just to have a type of "Saved"
79
79
  */
80
80
  assignIdCreatedUpdated<T extends BaseDBEntity>(obj: Partial<T>, opt?: CommonDaoOptions): T;
81
- /**
82
- * 1. Applies the patch
83
- * 2. If object is the same after patching - skips saving it
84
- * 3. Otherwise - saves the patched object and returns it
85
- *
86
- * Similar to `save` with skipIfEquals.
87
- * Similar to `patch`, but doesn't load the object from the Database.
88
- */
89
- savePatch(bm: BM, patch: Partial<BM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM>;
90
81
  /**
91
82
  * Convenience method to replace 3 operations (loading+patching+saving) with one:
92
83
  *
@@ -107,7 +98,7 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
107
98
  * Otherwise, similar behavior as patchById.
108
99
  * It still loads the row from the DB.
109
100
  */
110
- patch(bm: BM, patch: Partial<BM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM>;
101
+ patch(bm: BM, patch: Partial<BM>, opt?: CommonDaoPatchOptions<DBM>): Promise<BM>;
111
102
  /**
112
103
  * Like patch, but runs all operations within a Transaction.
113
104
  */
@@ -158,7 +149,8 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
158
149
  * Does NOT mutate the object.
159
150
  * Validates (unless `skipValidation=true` passed).
160
151
  */
161
- 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;
162
154
  getTableSchema(): Promise<JsonSchemaRootObject<DBM>>;
163
155
  createTable(schema: JsonSchemaObject<DBM>, opt?: CommonDaoCreateOptions): Promise<void>;
164
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;
@@ -444,27 +442,6 @@ class CommonDao {
444
442
  return obj;
445
443
  }
446
444
  // SAVE
447
- /**
448
- * 1. Applies the patch
449
- * 2. If object is the same after patching - skips saving it
450
- * 3. Otherwise - saves the patched object and returns it
451
- *
452
- * Similar to `save` with skipIfEquals.
453
- * Similar to `patch`, but doesn't load the object from the Database.
454
- */
455
- async savePatch(bm, patch, opt) {
456
- const patched = {
457
- ...bm,
458
- ...patch,
459
- };
460
- if ((0, js_lib_1._deepJsonEquals)(bm, patched)) {
461
- // Skipping the save operation, as data is the same
462
- return bm;
463
- }
464
- // Actually apply the patch by mutating the original object (by design)
465
- Object.assign(bm, patch);
466
- return await this.save(bm, opt);
467
- }
468
445
  /**
469
446
  * Convenience method to replace 3 operations (loading+patching+saving) with one:
470
447
  *
@@ -516,18 +493,28 @@ class CommonDao {
516
493
  // and should just continue as-is
517
494
  return await this.patchInTransaction(bm, patch, opt);
518
495
  }
519
- const loaded = await this.getById(bm.id, opt);
520
- if (loaded) {
521
- Object.assign(loaded, patch);
522
- if ((0, js_lib_1._deepJsonEquals)(loaded, bm)) {
496
+ if (opt.skipDBRead) {
497
+ const bmBefore = (0, js_lib_1._deepCopy)(bm);
498
+ Object.assign(bm, patch);
499
+ if ((0, js_lib_1._deepJsonEquals)(bm, bmBefore)) {
523
500
  // Skipping the save operation, as data is the same
524
501
  return bm;
525
502
  }
526
- // Make `bm` exactly the same as `loaded`
527
- (0, js_lib_1._objectAssignExact)(bm, loaded);
528
503
  }
529
504
  else {
530
- Object.assign(bm, patch);
505
+ const loaded = await this.getById(bm.id, opt);
506
+ if (loaded) {
507
+ Object.assign(loaded, patch);
508
+ if ((0, js_lib_1._deepJsonEquals)(loaded, bm)) {
509
+ // Skipping the save operation, as data is the same
510
+ return bm;
511
+ }
512
+ // Make `bm` exactly the same as `loaded`
513
+ (0, js_lib_1._objectAssignExact)(bm, loaded);
514
+ }
515
+ else {
516
+ Object.assign(bm, patch);
517
+ }
531
518
  }
532
519
  return await this.save(bm, opt);
533
520
  }
@@ -548,7 +535,7 @@ class CommonDao {
548
535
  // We compare with convertedBM, to account for cases when some extra property is assigned to bm,
549
536
  // which should be removed post-validation, but it breaks the "equality check"
550
537
  // Post-validation the equality check should work as intended
551
- const convertedBM = this.validateAndConvert(bm, this.cfg.bmSchema, opt);
538
+ const convertedBM = this.validateAndConvert(bm, this.cfg.bmSchema, 'save', opt);
552
539
  if ((0, js_lib_1._deepJsonEquals)(convertedBM, opt.skipIfEquals)) {
553
540
  // Skipping the save operation
554
541
  return bm;
@@ -693,7 +680,6 @@ class CommonDao {
693
680
  this.requireWriteAccess();
694
681
  const table = opt.table || this.cfg.table;
695
682
  opt.skipValidation ??= true;
696
- opt.skipConversion ??= true;
697
683
  opt.errorMode ||= js_lib_1.ErrorMode.SUPPRESS;
698
684
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
699
685
  opt = { ...opt, saveMethod: 'insert' };
@@ -828,7 +814,7 @@ class CommonDao {
828
814
  // DBM > BM
829
815
  const bm = ((await this.cfg.hooks.beforeDBMToBM?.(dbm)) || dbm);
830
816
  // Validate/convert BM
831
- return this.validateAndConvert(bm, this.cfg.bmSchema, opt);
817
+ return this.validateAndConvert(bm, this.cfg.bmSchema, 'load', opt);
832
818
  }
833
819
  async dbmsToBM(dbms, opt = {}) {
834
820
  return await (0, js_lib_1.pMap)(dbms, async (dbm) => await this.dbmToBM(dbm, opt));
@@ -837,7 +823,7 @@ class CommonDao {
837
823
  if (bm === undefined)
838
824
  return;
839
825
  // bm gets assigned to the new reference
840
- bm = this.validateAndConvert(bm, this.cfg.bmSchema, opt);
826
+ bm = this.validateAndConvert(bm, this.cfg.bmSchema, 'save', opt);
841
827
  // BM > DBM
842
828
  return ((await this.cfg.hooks.beforeBMToDBM?.(bm)) || bm);
843
829
  }
@@ -868,7 +854,8 @@ class CommonDao {
868
854
  * Does NOT mutate the object.
869
855
  * Validates (unless `skipValidation=true` passed).
870
856
  */
871
- validateAndConvert(obj, schema, opt = {}) {
857
+ validateAndConvert(obj, schema, op, // this is to skip validation if validateOnLoad/Save is false
858
+ opt = {}) {
872
859
  // Kirill 2021-10-18: I realized that there's little reason to keep removing null values
873
860
  // So, from now on we'll preserve them
874
861
  // "undefined" values, I believe, are/were not saved to/from DB anyway (due to e.g JSON.stringify removing them)
@@ -880,7 +867,10 @@ class CommonDao {
880
867
  // and they can be annoying with snapshot tests
881
868
  obj = (0, js_lib_1._filterUndefinedValues)(obj);
882
869
  // Return as is if no schema is passed or if `skipConversion` is set
883
- if (!schema || opt.skipConversion) {
870
+ if (!schema ||
871
+ opt.skipValidation ||
872
+ (op === 'load' && !this.cfg.validateOnLoad) ||
873
+ (op === 'save' && !this.cfg.validateOnSave)) {
884
874
  return obj;
885
875
  }
886
876
  // This will Convert and Validate
@@ -908,7 +898,7 @@ class CommonDao {
908
898
  convertedValue = vr.value;
909
899
  }
910
900
  // If we care about validation and there's an error
911
- if (error && !opt.skipValidation) {
901
+ if (error) {
912
902
  const processedError = this.cfg.hooks.onValidationError(error);
913
903
  if (processedError)
914
904
  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
  */
@@ -209,6 +211,12 @@ export interface CommonDaoSaveOptions<BM extends BaseDBEntity, DBM extends BaseD
209
211
  */
210
212
  skipIfEquals?: BM;
211
213
  }
214
+ export interface CommonDaoPatchOptions<DBM extends BaseDBEntity> extends CommonDaoSaveBatchOptions<DBM> {
215
+ /**
216
+ * If true - patch will skip loading from DB, and will just optimistically patch passed object.
217
+ */
218
+ skipDBRead?: boolean;
219
+ }
212
220
  /**
213
221
  * All properties default to undefined.
214
222
  */
@@ -235,10 +243,6 @@ export interface CommonDaoStreamOptions<IN> extends CommonDaoOptions, TransformL
235
243
  * @default true (for streams)
236
244
  */
237
245
  skipValidation?: boolean;
238
- /**
239
- * @default true (for streams)
240
- */
241
- skipConversion?: boolean;
242
246
  /**
243
247
  * @default ErrorMode.SUPPRESS for returning ReadableStream, because .pipe() has no concept of "error propagation"
244
248
  * @default ErrorMode.SUPPRESS for .forEach() streams as well, but overridable
@@ -4,6 +4,6 @@ exports.createTestTimeSeries = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  function createTestTimeSeries(count = 10) {
6
6
  const ts = Date.now();
7
- return (0, js_lib_1._range)(1, count + 1).map(i => [ts - i * 60000, (0, js_lib_1._randomInt)(10, 20)]);
7
+ return (0, js_lib_1._range)(1, count + 1).map(i => [ts - i * 60_000, (0, js_lib_1._randomInt)(10, 20)]);
8
8
  }
9
9
  exports.createTestTimeSeries = createTestTimeSeries;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "@naturalcycles/nodejs-lib": "^13.1.1"
9
9
  },
10
10
  "devDependencies": {
11
- "@naturalcycles/bench-lib": "^1.0.0",
11
+ "@naturalcycles/bench-lib": "^2.0.0",
12
12
  "@naturalcycles/dev-lib": "^13.0.0",
13
13
  "@types/node": "^20.2.1",
14
14
  "jest": "^29.0.0"
@@ -40,7 +40,7 @@
40
40
  "engines": {
41
41
  "node": ">=18.12"
42
42
  },
43
- "version": "9.7.2",
43
+ "version": "9.9.0",
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
  */
@@ -257,6 +260,14 @@ export interface CommonDaoSaveOptions<BM extends BaseDBEntity, DBM extends BaseD
257
260
  skipIfEquals?: BM
258
261
  }
259
262
 
263
+ export interface CommonDaoPatchOptions<DBM extends BaseDBEntity>
264
+ extends CommonDaoSaveBatchOptions<DBM> {
265
+ /**
266
+ * If true - patch will skip loading from DB, and will just optimistically patch passed object.
267
+ */
268
+ skipDBRead?: boolean
269
+ }
270
+
260
271
  /**
261
272
  * All properties default to undefined.
262
273
  */
@@ -294,11 +305,6 @@ export interface CommonDaoStreamOptions<IN>
294
305
  */
295
306
  skipValidation?: boolean
296
307
 
297
- /**
298
- * @default true (for streams)
299
- */
300
- skipConversion?: boolean
301
-
302
308
  /**
303
309
  * @default ErrorMode.SUPPRESS for returning ReadableStream, because .pipe() has no concept of "error propagation"
304
310
  * @default ErrorMode.SUPPRESS for .forEach() streams as well, but overridable
@@ -51,6 +51,7 @@ import {
51
51
  CommonDaoHooks,
52
52
  CommonDaoLogLevel,
53
53
  CommonDaoOptions,
54
+ CommonDaoPatchOptions,
54
55
  CommonDaoSaveBatchOptions,
55
56
  CommonDaoSaveOptions,
56
57
  CommonDaoStreamDeleteOptions,
@@ -80,6 +81,8 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
80
81
  assignGeneratedIds: false,
81
82
  useCreatedProperty: true,
82
83
  useUpdatedProperty: true,
84
+ validateOnLoad: true,
85
+ validateOnSave: true,
83
86
  logger: console,
84
87
  ...cfg,
85
88
  hooks: {
@@ -103,7 +106,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
103
106
  const bm = this.cfg.hooks!.beforeCreate!(part)
104
107
  // First assignIdCreatedUpdated, then validate!
105
108
  this.assignIdCreatedUpdated(bm, opt)
106
- return this.validateAndConvert(bm, this.cfg.bmSchema, opt)
109
+ return this.validateAndConvert(bm, this.cfg.bmSchema, undefined, opt)
107
110
  }
108
111
 
109
112
  // GET
@@ -352,7 +355,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
352
355
  ): Promise<void> {
353
356
  q.table = opt.table || q.table
354
357
  opt.skipValidation = opt.skipValidation !== false // default true
355
- opt.skipConversion = opt.skipConversion !== false // default true
356
358
  opt.errorMode ||= ErrorMode.SUPPRESS
357
359
 
358
360
  const partialQuery = !!q._selectedFieldNames
@@ -402,7 +404,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
402
404
  ): Promise<void> {
403
405
  q.table = opt.table || q.table
404
406
  opt.skipValidation = opt.skipValidation !== false // default true
405
- opt.skipConversion = opt.skipConversion !== false // default true
406
407
  opt.errorMode ||= ErrorMode.SUPPRESS
407
408
 
408
409
  const partialQuery = !!q._selectedFieldNames
@@ -451,7 +452,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
451
452
  streamQueryAsDBM(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<DBM> = {}): ReadableTyped<DBM> {
452
453
  q.table = opt.table || q.table
453
454
  opt.skipValidation = opt.skipValidation !== false // default true
454
- opt.skipConversion = opt.skipConversion !== false // default true
455
455
  opt.errorMode ||= ErrorMode.SUPPRESS
456
456
 
457
457
  const partialQuery = !!q._selectedFieldNames
@@ -490,7 +490,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
490
490
  streamQuery(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<BM> = {}): ReadableTyped<BM> {
491
491
  q.table = opt.table || q.table
492
492
  opt.skipValidation = opt.skipValidation !== false // default true
493
- opt.skipConversion = opt.skipConversion !== false // default true
494
493
  opt.errorMode ||= ErrorMode.SUPPRESS
495
494
 
496
495
  const stream = this.cfg.db.streamQuery<DBM>(q, opt)
@@ -604,31 +603,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
604
603
  }
605
604
 
606
605
  // SAVE
607
- /**
608
- * 1. Applies the patch
609
- * 2. If object is the same after patching - skips saving it
610
- * 3. Otherwise - saves the patched object and returns it
611
- *
612
- * Similar to `save` with skipIfEquals.
613
- * Similar to `patch`, but doesn't load the object from the Database.
614
- */
615
- async savePatch(bm: BM, patch: Partial<BM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM> {
616
- const patched: BM = {
617
- ...bm,
618
- ...patch,
619
- }
620
-
621
- if (_deepJsonEquals(bm, patched)) {
622
- // Skipping the save operation, as data is the same
623
- return bm
624
- }
625
-
626
- // Actually apply the patch by mutating the original object (by design)
627
- Object.assign(bm, patch)
628
-
629
- return await this.save(bm, opt)
630
- }
631
-
632
606
  /**
633
607
  * Convenience method to replace 3 operations (loading+patching+saving) with one:
634
608
  *
@@ -686,7 +660,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
686
660
  * Otherwise, similar behavior as patchById.
687
661
  * It still loads the row from the DB.
688
662
  */
689
- async patch(bm: BM, patch: Partial<BM>, opt: CommonDaoSaveBatchOptions<DBM> = {}): Promise<BM> {
663
+ async patch(bm: BM, patch: Partial<BM>, opt: CommonDaoPatchOptions<DBM> = {}): Promise<BM> {
690
664
  if (this.cfg.patchInTransaction && !opt.tx) {
691
665
  // patchInTransaction means that we should run this op in Transaction
692
666
  // But if opt.tx is passed - means that we are already in a Transaction,
@@ -694,20 +668,29 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
694
668
  return await this.patchInTransaction(bm, patch, opt)
695
669
  }
696
670
 
697
- const loaded = await this.getById(bm.id, opt)
698
-
699
- if (loaded) {
700
- Object.assign(loaded, patch)
701
-
702
- if (_deepJsonEquals(loaded, bm)) {
671
+ if (opt.skipDBRead) {
672
+ const bmBefore = _deepCopy(bm)
673
+ Object.assign(bm, patch)
674
+ if (_deepJsonEquals(bm, bmBefore)) {
703
675
  // Skipping the save operation, as data is the same
704
676
  return bm
705
677
  }
706
-
707
- // Make `bm` exactly the same as `loaded`
708
- _objectAssignExact(bm, loaded)
709
678
  } else {
710
- Object.assign(bm, patch)
679
+ const loaded = await this.getById(bm.id, opt)
680
+
681
+ if (loaded) {
682
+ Object.assign(loaded, patch)
683
+
684
+ if (_deepJsonEquals(loaded, bm)) {
685
+ // Skipping the save operation, as data is the same
686
+ return bm
687
+ }
688
+
689
+ // Make `bm` exactly the same as `loaded`
690
+ _objectAssignExact(bm, loaded)
691
+ } else {
692
+ Object.assign(bm, patch)
693
+ }
711
694
  }
712
695
 
713
696
  return await this.save(bm, opt)
@@ -736,7 +719,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
736
719
  // We compare with convertedBM, to account for cases when some extra property is assigned to bm,
737
720
  // which should be removed post-validation, but it breaks the "equality check"
738
721
  // Post-validation the equality check should work as intended
739
- const convertedBM = this.validateAndConvert(bm as Partial<BM>, this.cfg.bmSchema, opt)
722
+ const convertedBM = this.validateAndConvert(bm as Partial<BM>, this.cfg.bmSchema, 'save', opt)
740
723
  if (_deepJsonEquals(convertedBM, opt.skipIfEquals)) {
741
724
  // Skipping the save operation
742
725
  return bm as BM
@@ -915,7 +898,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
915
898
 
916
899
  const table = opt.table || this.cfg.table
917
900
  opt.skipValidation ??= true
918
- opt.skipConversion ??= true
919
901
  opt.errorMode ||= ErrorMode.SUPPRESS
920
902
 
921
903
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
@@ -1092,7 +1074,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1092
1074
  const bm = ((await this.cfg.hooks!.beforeDBMToBM?.(dbm)) || dbm) as Partial<BM>
1093
1075
 
1094
1076
  // Validate/convert BM
1095
- return this.validateAndConvert(bm, this.cfg.bmSchema, opt)
1077
+ return this.validateAndConvert(bm, this.cfg.bmSchema, 'load', opt)
1096
1078
  }
1097
1079
 
1098
1080
  async dbmsToBM(dbms: DBM[], opt: CommonDaoOptions = {}): Promise<BM[]> {
@@ -1109,7 +1091,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1109
1091
  if (bm === undefined) return
1110
1092
 
1111
1093
  // bm gets assigned to the new reference
1112
- bm = this.validateAndConvert(bm, this.cfg.bmSchema, opt)
1094
+ bm = this.validateAndConvert(bm, this.cfg.bmSchema, 'save', opt)
1113
1095
 
1114
1096
  // BM > DBM
1115
1097
  return ((await this.cfg.hooks!.beforeBMToDBM?.(bm!)) || bm) as DBM
@@ -1153,6 +1135,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1153
1135
  validateAndConvert<T>(
1154
1136
  obj: Partial<T>,
1155
1137
  schema: ObjectSchema<T> | AjvSchema<T> | ZodSchema<T> | undefined,
1138
+ op?: 'load' | 'save', // this is to skip validation if validateOnLoad/Save is false
1156
1139
  opt: CommonDaoOptions = {},
1157
1140
  ): any {
1158
1141
  // Kirill 2021-10-18: I realized that there's little reason to keep removing null values
@@ -1167,7 +1150,12 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1167
1150
  obj = _filterUndefinedValues(obj)
1168
1151
 
1169
1152
  // Return as is if no schema is passed or if `skipConversion` is set
1170
- if (!schema || opt.skipConversion) {
1153
+ if (
1154
+ !schema ||
1155
+ opt.skipValidation ||
1156
+ (op === 'load' && !this.cfg.validateOnLoad) ||
1157
+ (op === 'save' && !this.cfg.validateOnSave)
1158
+ ) {
1171
1159
  return obj
1172
1160
  }
1173
1161
 
@@ -1198,7 +1186,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1198
1186
  }
1199
1187
 
1200
1188
  // If we care about validation and there's an error
1201
- if (error && !opt.skipValidation) {
1189
+ if (error) {
1202
1190
  const processedError = this.cfg.hooks!.onValidationError!(error)
1203
1191
 
1204
1192
  if (processedError) throw processedError