@naturalcycles/db-lib 9.5.0 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/db-lib",
3
3
  "scripts": {
4
- "prepare": "husky install"
4
+ "prepare": "husky"
5
5
  },
6
6
  "dependencies": {
7
7
  "@naturalcycles/js-lib": "^14.116.0",
@@ -40,7 +40,7 @@
40
40
  "engines": {
41
41
  "node": ">=18.12"
42
42
  },
43
- "version": "9.5.0",
43
+ "version": "9.7.0",
44
44
  "description": "Lowest Common Denominator API to supported Databases",
45
45
  "keywords": [
46
46
  "db",
@@ -47,17 +47,6 @@ export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntit
47
47
  */
48
48
  beforeCreate: (bm: Partial<BM>) => Partial<BM>
49
49
 
50
- /**
51
- * Called when loading things "as DBM" and validation is not skipped.
52
- * When loading things as BM/TM - other hooks get involved instead:
53
- * - beforeDBMToBM
54
- * - beforeBMToTM
55
- *
56
- * TODO: maybe rename those to `validateAs(model)`
57
- * as it only validates "final state", not intermediate
58
- */
59
- beforeDBMValidate: (dbm: Partial<DBM>) => Partial<DBM>
60
-
61
50
  beforeDBMToBM: (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>
62
51
  beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>
63
52
 
@@ -136,7 +125,6 @@ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity
136
125
  /**
137
126
  * Joi, AjvSchema or ZodSchema is supported.
138
127
  */
139
- dbmSchema?: ObjectSchema<DBM> | AjvSchema<DBM> | ZodSchema<DBM>
140
128
  bmSchema?: ObjectSchema<BM> | AjvSchema<BM> | ZodSchema<BM>
141
129
 
142
130
  excludeFromIndexes?: (keyof DBM)[]
@@ -201,17 +189,6 @@ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity
201
189
  */
202
190
  useUpdatedProperty?: boolean
203
191
 
204
- /**
205
- * Default is false.
206
- * If true - will run `_filterNullishValues` inside `validateAndConvert` function
207
- * (instead of `_filterUndefinedValues`).
208
- * This was the old db-lib behavior.
209
- * This option allows to keep backwards-compatible behavior.
210
- *
211
- * @deprecated
212
- */
213
- filterNullishValues?: boolean
214
-
215
192
  /**
216
193
  * Defaults to false.
217
194
  * If true - run patch operations (patch, patchById) in a Transaction.
@@ -242,16 +219,6 @@ export interface CommonDaoOptions extends CommonDBOptions {
242
219
  */
243
220
  skipConversion?: boolean
244
221
 
245
- /**
246
- * If true - will SKIP ANY transformation/processing, will return DB objects as they are. Will also skip created/updated/id
247
- * generation.
248
- *
249
- * Useful for performance/streaming/pipelines.
250
- *
251
- * @default false
252
- */
253
- raw?: boolean
254
-
255
222
  /**
256
223
  * @default false
257
224
  */
@@ -1,8 +1,8 @@
1
1
  import { Transform } from 'node:stream'
2
2
  import {
3
3
  _assert,
4
+ _deepCopy,
4
5
  _deepJsonEquals,
5
- _filterNullishValues,
6
6
  _filterUndefinedValues,
7
7
  _isTruthy,
8
8
  _objectAssignExact,
@@ -43,13 +43,7 @@ import {
43
43
  writableVoid,
44
44
  } from '@naturalcycles/nodejs-lib'
45
45
  import { DBLibError } from '../cnst'
46
- import {
47
- CommonDBTransactionOptions,
48
- DBModelType,
49
- DBPatch,
50
- DBTransaction,
51
- RunQueryResult,
52
- } from '../db.model'
46
+ import { CommonDBTransactionOptions, DBPatch, DBTransaction, RunQueryResult } from '../db.model'
53
47
  import { DBQuery, RunnableDBQuery } from '../query/dbQuery'
54
48
  import {
55
49
  CommonDaoCfg,
@@ -91,9 +85,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
91
85
  hooks: {
92
86
  parseNaturalId: () => ({}),
93
87
  beforeCreate: bm => bm as BM,
94
- beforeDBMValidate: dbm => dbm,
95
- beforeDBMToBM: dbm => dbm as any,
96
- beforeBMToDBM: bm => bm as any,
97
88
  anonymize: dbm => dbm,
98
89
  onValidationError: err => err,
99
90
  ...cfg.hooks,
@@ -112,7 +103,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
112
103
  const bm = this.cfg.hooks!.beforeCreate!(part)
113
104
  // First assignIdCreatedUpdated, then validate!
114
105
  this.assignIdCreatedUpdated(bm, opt)
115
- return this.validateAndConvert(bm, this.cfg.bmSchema, DBModelType.BM, opt)
106
+ return this.validateAndConvert(bm, this.cfg.bmSchema, opt)
116
107
  }
117
108
 
118
109
  // GET
@@ -125,11 +116,11 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
125
116
  const started = this.logStarted(op, table)
126
117
 
127
118
  let dbm = (await (opt.tx || this.cfg.db).getByIds<DBM>(table, [id]))[0]
128
- if (dbm && !opt.raw && this.cfg.hooks!.afterLoad) {
119
+ if (dbm && this.cfg.hooks!.afterLoad) {
129
120
  dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
130
121
  }
131
122
 
132
- const bm = opt.raw ? (dbm as any) : await this.dbmToBM(dbm, opt)
123
+ const bm = await this.dbmToBM(dbm, opt)
133
124
  this.logResult(started, op, bm, table)
134
125
  return bm || null
135
126
  }
@@ -141,18 +132,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
141
132
  return this.create({ ...part, id }, opt)
142
133
  }
143
134
 
144
- async getByIdAsDBMOrEmpty(
145
- id: string,
146
- part: Partial<BM> = {},
147
- opt?: CommonDaoOptions,
148
- ): Promise<DBM> {
149
- const dbm = await this.getByIdAsDBM(id, opt)
150
- if (dbm) return dbm
151
-
152
- const bm = this.create({ ...part, id }, opt)
153
- return await this.bmToDBM(bm, opt)
154
- }
155
-
156
135
  async getByIdAsDBM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
157
136
  async getByIdAsDBM(id?: string | null, opt?: CommonDaoOptions): Promise<DBM | null>
158
137
  async getByIdAsDBM(id?: string | null, opt: CommonDaoOptions = {}): Promise<DBM | null> {
@@ -161,13 +140,11 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
161
140
  const table = opt.table || this.cfg.table
162
141
  const started = this.logStarted(op, table)
163
142
  let [dbm] = await (opt.tx || this.cfg.db).getByIds<DBM>(table, [id])
164
- if (dbm && !opt.raw && this.cfg.hooks!.afterLoad) {
143
+ if (dbm && this.cfg.hooks!.afterLoad) {
165
144
  dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
166
145
  }
167
146
 
168
- if (!opt.raw) {
169
- dbm = this.anyToDBM(dbm!, opt)
170
- }
147
+ dbm = this.anyToDBM(dbm!, opt)
171
148
  this.logResult(started, op, dbm, table)
172
149
  return dbm || null
173
150
  }
@@ -178,13 +155,13 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
178
155
  const table = opt.table || this.cfg.table
179
156
  const started = this.logStarted(op, table)
180
157
  let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids)
181
- if (!opt.raw && this.cfg.hooks!.afterLoad && dbms.length) {
158
+ if (this.cfg.hooks!.afterLoad && dbms.length) {
182
159
  dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
183
160
  _isTruthy,
184
161
  )
185
162
  }
186
163
 
187
- const bms = opt.raw ? (dbms as any) : await this.dbmsToBM(dbms, opt)
164
+ const bms = await this.dbmsToBM(dbms, opt)
188
165
  this.logResult(started, op, bms, table)
189
166
  return bms
190
167
  }
@@ -195,7 +172,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
195
172
  const table = opt.table || this.cfg.table
196
173
  const started = this.logStarted(op, table)
197
174
  let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids)
198
- if (!opt.raw && this.cfg.hooks!.afterLoad && dbms.length) {
175
+ if (this.cfg.hooks!.afterLoad && dbms.length) {
199
176
  dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
200
177
  _isTruthy,
201
178
  )
@@ -318,13 +295,13 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
318
295
  const started = this.logStarted(op, q.table)
319
296
  let { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
320
297
  const partialQuery = !!q._selectedFieldNames
321
- if (!opt.raw && this.cfg.hooks!.afterLoad && rows.length) {
298
+ if (this.cfg.hooks!.afterLoad && rows.length) {
322
299
  rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
323
300
  _isTruthy,
324
301
  )
325
302
  }
326
303
 
327
- const bms = partialQuery || opt.raw ? (rows as any[]) : await this.dbmsToBM(rows, opt)
304
+ const bms = partialQuery ? (rows as any[]) : await this.dbmsToBM(rows, opt)
328
305
  this.logResult(started, op, bms, q.table)
329
306
  return {
330
307
  rows: bms,
@@ -345,14 +322,14 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
345
322
  const op = `runQueryAsDBM(${q.pretty()})`
346
323
  const started = this.logStarted(op, q.table)
347
324
  let { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
348
- if (!opt.raw && this.cfg.hooks!.afterLoad && rows.length) {
325
+ if (this.cfg.hooks!.afterLoad && rows.length) {
349
326
  rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
350
327
  _isTruthy,
351
328
  )
352
329
  }
353
330
 
354
331
  const partialQuery = !!q._selectedFieldNames
355
- const dbms = partialQuery || opt.raw ? rows : this.anyToDBMs(rows, opt)
332
+ const dbms = partialQuery ? rows : this.anyToDBMs(rows, opt)
356
333
  this.logResult(started, op, dbms, q.table)
357
334
  return { rows: dbms, ...queryResult }
358
335
  }
@@ -388,7 +365,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
388
365
  transformMap<DBM, BM>(
389
366
  async dbm => {
390
367
  count++
391
- if (partialQuery || opt.raw) return dbm as any
368
+ if (partialQuery) return dbm as any
392
369
 
393
370
  if (this.cfg.hooks!.afterLoad) {
394
371
  dbm = (await this.cfg.hooks!.afterLoad(dbm))!
@@ -438,7 +415,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
438
415
  transformMap<any, DBM>(
439
416
  async dbm => {
440
417
  count++
441
- if (partialQuery || opt.raw) return dbm
418
+ if (partialQuery) return dbm
442
419
 
443
420
  if (this.cfg.hooks!.afterLoad) {
444
421
  dbm = (await this.cfg.hooks!.afterLoad(dbm))!
@@ -480,7 +457,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
480
457
  const partialQuery = !!q._selectedFieldNames
481
458
 
482
459
  const stream = this.cfg.db.streamQuery<DBM>(q, opt)
483
- if (partialQuery || opt.raw) return stream
460
+ if (partialQuery) return stream
484
461
 
485
462
  return stream
486
463
  .on('error', err => stream.emit('error', err))
@@ -518,7 +495,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
518
495
 
519
496
  const stream = this.cfg.db.streamQuery<DBM>(q, opt)
520
497
  const partialQuery = !!q._selectedFieldNames
521
- if (partialQuery || opt.raw) return stream
498
+ if (partialQuery) return stream
522
499
 
523
500
  return (
524
501
  stream
@@ -627,51 +604,6 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
627
604
  }
628
605
 
629
606
  // SAVE
630
- /**
631
- * Mutates with id, created, updated
632
- */
633
- async save(bm: Unsaved<BM>, opt: CommonDaoSaveOptions<BM, DBM> = {}): Promise<BM> {
634
- this.requireWriteAccess()
635
-
636
- if (opt.skipIfEquals && _deepJsonEquals(bm, opt.skipIfEquals)) {
637
- // Skipping the save operation
638
- return bm as BM
639
- }
640
-
641
- const idWasGenerated = !bm.id && this.cfg.generateId
642
- this.assignIdCreatedUpdated(bm, opt) // mutates
643
- _typeCast<BM>(bm)
644
- let dbm = await this.bmToDBM(bm, opt)
645
-
646
- if (this.cfg.hooks!.beforeSave) {
647
- dbm = (await this.cfg.hooks!.beforeSave(dbm))!
648
- if (dbm === null) return bm
649
- }
650
-
651
- const table = opt.table || this.cfg.table
652
- if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
653
- if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
654
- opt = { ...opt, saveMethod: 'insert' }
655
- }
656
- const op = `save(${dbm.id})`
657
- const started = this.logSaveStarted(op, bm, table)
658
- const { excludeFromIndexes } = this.cfg
659
- const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
660
-
661
- await (opt.tx || this.cfg.db).saveBatch(table, [dbm], {
662
- excludeFromIndexes,
663
- assignGeneratedIds,
664
- ...opt,
665
- })
666
-
667
- if (assignGeneratedIds) {
668
- bm.id = dbm.id
669
- }
670
-
671
- this.logSaveResult(started, op, table)
672
- return bm
673
- }
674
-
675
607
  /**
676
608
  * 1. Applies the patch
677
609
  * 2. If object is the same after patching - skips saving it
@@ -794,19 +726,62 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
794
726
  })
795
727
  }
796
728
 
797
- async saveAsDBM(dbm: Unsaved<DBM>, opt: CommonDaoSaveBatchOptions<DBM> = {}): Promise<DBM> {
729
+ /**
730
+ * Mutates with id, created, updated
731
+ */
732
+ async save(bm: Unsaved<BM>, opt: CommonDaoSaveOptions<BM, DBM> = {}): Promise<BM> {
733
+ this.requireWriteAccess()
734
+
735
+ if (opt.skipIfEquals && _deepJsonEquals(bm, opt.skipIfEquals)) {
736
+ // Skipping the save operation
737
+ return bm as BM
738
+ }
739
+
740
+ const idWasGenerated = !bm.id && this.cfg.generateId
741
+ this.assignIdCreatedUpdated(bm, opt) // mutates
742
+ _typeCast<BM>(bm)
743
+ let dbm = await this.bmToDBM(bm, opt) // validates BM
744
+
745
+ if (this.cfg.hooks!.beforeSave) {
746
+ dbm = (await this.cfg.hooks!.beforeSave(dbm))!
747
+ if (dbm === null) return bm
748
+ }
749
+
750
+ const table = opt.table || this.cfg.table
751
+ if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
752
+ if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
753
+ opt = { ...opt, saveMethod: 'insert' }
754
+ }
755
+ const op = `save(${dbm.id})`
756
+ const started = this.logSaveStarted(op, bm, table)
757
+ const { excludeFromIndexes } = this.cfg
758
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
759
+
760
+ await (opt.tx || this.cfg.db).saveBatch(table, [dbm], {
761
+ excludeFromIndexes,
762
+ assignGeneratedIds,
763
+ ...opt,
764
+ })
765
+
766
+ if (assignGeneratedIds) {
767
+ bm.id = dbm.id
768
+ }
769
+
770
+ this.logSaveResult(started, op, table)
771
+ return bm
772
+ }
773
+
774
+ async saveAsDBM(dbm: Unsaved<DBM>, opt: CommonDaoSaveOptions<BM, DBM> = {}): Promise<DBM> {
798
775
  this.requireWriteAccess()
799
776
  const table = opt.table || this.cfg.table
800
777
 
801
778
  // assigning id in case it misses the id
802
779
  // will override/set `updated` field, unless opts.preserveUpdated is set
803
- let row = dbm as DBM
804
- if (!opt.raw) {
805
- const idWasGenerated = !dbm.id && this.cfg.generateId
806
- this.assignIdCreatedUpdated(dbm, opt) // mutates
807
- row = this.anyToDBM(dbm, opt)
808
- if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, row)
809
- }
780
+ const idWasGenerated = !dbm.id && this.cfg.generateId
781
+ this.assignIdCreatedUpdated(dbm, opt) // mutates
782
+ let row = this.anyToDBM(dbm, opt)
783
+ if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, row)
784
+
810
785
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
811
786
  opt = { ...opt, saveMethod: 'insert' }
812
787
  }
@@ -885,12 +860,10 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
885
860
  if (!dbms.length) return []
886
861
  this.requireWriteAccess()
887
862
  const table = opt.table || this.cfg.table
888
- let rows = dbms as DBM[]
889
- if (!opt.raw) {
890
- dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)) // mutates
891
- rows = this.anyToDBMs(dbms as DBM[], opt)
892
- if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
893
- }
863
+ dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)) // mutates
864
+ let rows = this.anyToDBMs(dbms as DBM[], opt)
865
+ if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
866
+
894
867
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
895
868
  opt = { ...opt, saveMethod: 'insert' }
896
869
  }
@@ -995,18 +968,9 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
995
968
  /**
996
969
  * @returns number of deleted items
997
970
  */
998
- async deleteById(id: undefined | null, opt?: CommonDaoOptions): Promise<0>
999
- async deleteById(id?: string | null, opt?: CommonDaoOptions): Promise<number>
1000
971
  async deleteById(id?: string | null, opt: CommonDaoOptions = {}): Promise<number> {
1001
972
  if (!id) return 0
1002
- this.requireWriteAccess()
1003
- this.requireObjectMutability(opt)
1004
- const op = `deleteById(${id})`
1005
- const table = opt.table || this.cfg.table
1006
- const started = this.logStarted(op, table)
1007
- const count = await this.cfg.db.deleteByIds(table, [id], opt)
1008
- this.logSaveResult(started, op, table)
1009
- return count
973
+ return await this.deleteByIds([id], opt)
1010
974
  }
1011
975
 
1012
976
  async deleteByIds(ids: string[], opt: CommonDaoOptions = {}): Promise<number> {
@@ -1119,11 +1083,16 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1119
1083
  }
1120
1084
 
1121
1085
  // DBM > BM
1122
- const bm = await this.cfg.hooks!.beforeDBMToBM!(dbm)
1086
+ let bm: Partial<BM>
1087
+ if (this.cfg.hooks!.beforeDBMToBM) {
1088
+ bm = await this.cfg.hooks!.beforeDBMToBM(dbm)
1089
+ } else {
1090
+ bm = dbm as any
1091
+ }
1123
1092
 
1124
1093
  // Validate/convert BM
1125
1094
 
1126
- return this.validateAndConvert(bm, this.cfg.bmSchema, DBModelType.BM, opt)
1095
+ return this.validateAndConvert(bm, this.cfg.bmSchema, opt)
1127
1096
  }
1128
1097
 
1129
1098
  async dbmsToBM(dbms: DBM[], opt: CommonDaoOptions = {}): Promise<BM[]> {
@@ -1139,20 +1108,23 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1139
1108
  async bmToDBM(bm?: BM, opt?: CommonDaoOptions): Promise<DBM | undefined> {
1140
1109
  if (bm === undefined) return
1141
1110
 
1142
- // optimization: no need to run the BM validation, since DBM will be validated anyway
1143
- // Validate/convert BM
1144
- // bm gets assigned to the new reference
1145
- // bm = this.validateAndConvert(bm, this.cfg.bmSchema, DBModelType.BM, opt)
1146
-
1147
1111
  // should not do it on load, but only on save!
1148
1112
  // this.assignIdCreatedUpdated(bm, opt)
1149
1113
 
1114
+ // bm gets assigned to the new reference
1115
+ bm = this.validateAndConvert(bm, this.cfg.bmSchema, opt)
1116
+
1150
1117
  // BM > DBM
1151
- const dbm = { ...(await this.cfg.hooks!.beforeBMToDBM!(bm)) }
1118
+ let dbm: DBM
1119
+ if (this.cfg.hooks!.beforeBMToDBM) {
1120
+ dbm = { ...((await this.cfg.hooks!.beforeBMToDBM(bm!)) as DBM) }
1121
+ } else {
1122
+ dbm = bm as any
1123
+ }
1152
1124
 
1153
1125
  // Validate/convert DBM
1154
-
1155
- return this.validateAndConvert(dbm, this.cfg.dbmSchema, DBModelType.DBM, opt)
1126
+ // return this.validateAndConvert(dbm, this.cfg.dbmSchema, DBModelType.DBM, opt)
1127
+ return dbm
1156
1128
  }
1157
1129
 
1158
1130
  async bmsToDBM(bms: BM[], opt: CommonDaoOptions = {}): Promise<DBM[]> {
@@ -1170,12 +1142,15 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1170
1142
 
1171
1143
  dbm = { ...dbm, ...this.cfg.hooks!.parseNaturalId!(dbm.id) }
1172
1144
 
1145
+ // todo: is this the right place?
1146
+ // todo: is anyToDBM even needed?
1173
1147
  if (opt.anonymize) {
1174
1148
  dbm = this.cfg.hooks!.anonymize!(dbm)
1175
1149
  }
1176
1150
 
1177
1151
  // Validate/convert DBM
1178
- return this.validateAndConvert(dbm, this.cfg.dbmSchema, DBModelType.DBM, opt)
1152
+ // return this.validateAndConvert(dbm, this.cfg.dbmSchema, DBModelType.DBM, opt)
1153
+ return dbm
1179
1154
  }
1180
1155
 
1181
1156
  anyToDBMs(entities: DBM[], opt: CommonDaoOptions = {}): DBM[] {
@@ -1191,12 +1166,8 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1191
1166
  validateAndConvert<T>(
1192
1167
  obj: Partial<T>,
1193
1168
  schema: ObjectSchema<T> | AjvSchema<T> | ZodSchema<T> | undefined,
1194
- modelType?: DBModelType,
1195
1169
  opt: CommonDaoOptions = {},
1196
1170
  ): any {
1197
- // `raw` option completely bypasses any processing
1198
- if (opt.raw) return obj as any
1199
-
1200
1171
  // Kirill 2021-10-18: I realized that there's little reason to keep removing null values
1201
1172
  // So, from now on we'll preserve them
1202
1173
  // "undefined" values, I believe, are/were not saved to/from DB anyway (due to e.g JSON.stringify removing them)
@@ -1206,16 +1177,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1206
1177
  // obj = _filterNullishValues(obj as any)
1207
1178
  // We still filter `undefined` values here, because `beforeDBMToBM` can return undefined values
1208
1179
  // and they can be annoying with snapshot tests
1209
- if (this.cfg.filterNullishValues) {
1210
- obj = _filterNullishValues(obj)
1211
- } else {
1212
- obj = _filterUndefinedValues(obj)
1213
- }
1214
-
1215
- // Pre-validation hooks
1216
- if (modelType === DBModelType.DBM) {
1217
- obj = this.cfg.hooks!.beforeDBMValidate!(obj as any) as T
1218
- }
1180
+ obj = _filterUndefinedValues(obj)
1219
1181
 
1220
1182
  // Return as is if no schema is passed or if `skipConversion` is set
1221
1183
  if (!schema || opt.skipConversion) {
@@ -1224,7 +1186,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1224
1186
 
1225
1187
  // This will Convert and Validate
1226
1188
  const table = opt.table || this.cfg.table
1227
- const objectName = table + (modelType || '')
1189
+ const objectName = table
1228
1190
 
1229
1191
  let error: JoiValidationError | AjvValidationError | ZodValidationError<T> | undefined
1230
1192
  let convertedValue: any
@@ -1413,9 +1375,9 @@ export class CommonDaoTransaction {
1413
1375
  async save<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
1414
1376
  dao: CommonDao<BM, DBM>,
1415
1377
  bm: Unsaved<BM>,
1416
- opt?: CommonDaoSaveBatchOptions<DBM>,
1378
+ opt?: CommonDaoSaveOptions<BM, DBM>,
1417
1379
  ): Promise<BM> {
1418
- return (await this.saveBatch(dao, [bm], opt))[0]!
1380
+ return await dao.save(bm, { ...opt, tx: this.tx })
1419
1381
  }
1420
1382
 
1421
1383
  async saveBatch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
@@ -1426,6 +1388,24 @@ export class CommonDaoTransaction {
1426
1388
  return await dao.saveBatch(bms, { ...opt, tx: this.tx })
1427
1389
  }
1428
1390
 
1391
+ /**
1392
+ * DaoTransaction.patch does not load from DB.
1393
+ * It assumes the bm was previously loaded in the same Transaction, hence could not be
1394
+ * concurrently modified. Hence it's safe to not sync with DB.
1395
+ *
1396
+ * So, this method is a rather simple convenience "Object.assign and then save".
1397
+ */
1398
+ async patch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
1399
+ dao: CommonDao<BM, DBM>,
1400
+ bm: BM,
1401
+ patch: Partial<BM>,
1402
+ opt?: CommonDaoSaveOptions<BM, DBM>,
1403
+ ): Promise<BM> {
1404
+ const skipIfEquals = _deepCopy(bm)
1405
+ Object.assign(bm, patch)
1406
+ return await dao.save(bm, { ...opt, skipIfEquals, tx: this.tx })
1407
+ }
1408
+
1429
1409
  async deleteById(
1430
1410
  dao: CommonDao<any>,
1431
1411
  id?: string | null,
@@ -8,10 +8,9 @@ import { CommonDBImplementationQuirks, expectMatch } from './dbTest'
8
8
  import {
9
9
  createTestItemsBM,
10
10
  testItemBMSchema,
11
- testItemDBMSchema,
12
11
  TEST_TABLE,
13
12
  createTestItemBM,
14
- testItemDBMJsonSchema,
13
+ testItemBMJsonSchema,
15
14
  } from './test.model'
16
15
  import { TestItemBM } from '.'
17
16
 
@@ -20,7 +19,6 @@ export function runCommonDaoTest(db: CommonDB, quirks: CommonDBImplementationQui
20
19
  const dao = new CommonDao({
21
20
  table: TEST_TABLE,
22
21
  db,
23
- dbmSchema: testItemDBMSchema,
24
22
  bmSchema: testItemBMSchema,
25
23
  logStarted: true,
26
24
  logLevel: CommonDaoLogLevel.DATA_FULL,
@@ -43,7 +41,7 @@ export function runCommonDaoTest(db: CommonDB, quirks: CommonDBImplementationQui
43
41
  // CREATE TABLE, DROP
44
42
  if (support.createTable) {
45
43
  test('createTable, dropIfExists=true', async () => {
46
- await dao.createTable(testItemDBMJsonSchema, { dropIfExists: true })
44
+ await dao.createTable(testItemBMJsonSchema, { dropIfExists: true })
47
45
  })
48
46
  }
49
47
 
@@ -7,8 +7,8 @@ import {
7
7
  createTestItemDBM,
8
8
  createTestItemsDBM,
9
9
  TEST_TABLE,
10
+ testItemBMJsonSchema,
10
11
  TestItemDBM,
11
- testItemDBMJsonSchema,
12
12
  } from './test.model'
13
13
  import { deepFreeze } from './test.util'
14
14
 
@@ -42,7 +42,7 @@ export function runCommonDBTest(db: CommonDB, quirks: CommonDBImplementationQuir
42
42
  // CREATE TABLE, DROP
43
43
  if (support.createTable) {
44
44
  test('createTable, dropIfExists=true', async () => {
45
- await db.createTable(TEST_TABLE, testItemDBMJsonSchema, { dropIfExists: true })
45
+ await db.createTable(TEST_TABLE, testItemBMJsonSchema, { dropIfExists: true })
46
46
  })
47
47
  }
48
48
 
@@ -10,8 +10,6 @@ import {
10
10
  testItemBMJsonSchema,
11
11
  testItemBMSchema,
12
12
  TestItemDBM,
13
- testItemDBMJsonSchema,
14
- testItemDBMSchema,
15
13
  TestItemTM,
16
14
  testItemTMSchema,
17
15
  TEST_TABLE,
@@ -25,11 +23,9 @@ export {
25
23
  createTestItemBM,
26
24
  createTestItemsDBM,
27
25
  createTestItemsBM,
28
- testItemDBMSchema,
29
26
  testItemBMSchema,
30
27
  testItemTMSchema,
31
28
  testItemBMJsonSchema,
32
- testItemDBMJsonSchema,
33
29
  runCommonDBTest,
34
30
  runCommonDaoTest,
35
31
  runCommonKeyValueDBTest,
@@ -35,14 +35,6 @@ export const testItemBMSchema = objectSchema<TestItemBM>({
35
35
  b1: binarySchema.optional(),
36
36
  }).concat(baseDBEntitySchema as any)
37
37
 
38
- export const testItemDBMSchema = objectSchema<TestItemDBM>({
39
- k1: stringSchema,
40
- k2: stringSchema.allow(null).optional(),
41
- k3: numberSchema.optional(),
42
- even: booleanSchema.optional(),
43
- b1: binarySchema.optional(),
44
- }).concat(baseDBEntitySchema as any)
45
-
46
38
  export const testItemTMSchema = objectSchema<TestItemTM>({
47
39
  k1: stringSchema,
48
40
  even: booleanSchema.optional(),
@@ -63,20 +55,6 @@ export const testItemBMJsonSchema: JsonSchemaObject<TestItemBM> = jsonSchema
63
55
  .baseDBEntity()
64
56
  .build()
65
57
 
66
- export const testItemDBMJsonSchema: JsonSchemaObject<TestItemDBM> = jsonSchema
67
- .rootObject<TestItemDBM>({
68
- // todo: figure out how to not copy-paste these 3 fields
69
- id: jsonSchema.string(),
70
- created: jsonSchema.unixTimestamp(),
71
- updated: jsonSchema.unixTimestamp(),
72
- k1: jsonSchema.string(),
73
- k2: jsonSchema.string().optional(),
74
- k3: jsonSchema.number().optional(),
75
- even: jsonSchema.boolean().optional(),
76
- b1: jsonSchema.buffer().optional(),
77
- })
78
- .build()
79
-
80
58
  export function createTestItemDBM(num = 1): TestItemDBM {
81
59
  return {
82
60
  id: `id${num}`,