@naturalcycles/db-lib 10.45.2 → 10.46.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.
@@ -123,16 +123,16 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
123
123
  * @experimental
124
124
  */
125
125
  incrementBatch(prop: keyof DBM, incrementMap: StringMap<number>, opt?: CommonDaoOptions): Promise<StringMap<number>>;
126
- dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): Promise<null>;
127
- dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): Promise<BM>;
128
- dbmsToBM(dbms: DBM[], opt?: CommonDaoOptions): Promise<BM[]>;
126
+ dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): null;
127
+ dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): BM;
128
+ dbmsToBM(dbms: DBM[], opt?: CommonDaoOptions): BM[];
129
129
  /**
130
130
  * Mutates object with properties: id, created, updated.
131
131
  * Returns DBM (new reference).
132
132
  */
133
- bmToDBM(bm: undefined, opt?: CommonDaoOptions): Promise<null>;
134
- bmToDBM(bm?: BM, opt?: CommonDaoOptions): Promise<DBM>;
135
- bmsToDBM(bms: BM[], opt?: CommonDaoOptions): Promise<DBM[]>;
133
+ bmToDBM(bm: undefined, opt?: CommonDaoOptions): null;
134
+ bmToDBM(bm?: BM, opt?: CommonDaoOptions): DBM;
135
+ bmsToDBM(bms: BM[], opt?: CommonDaoOptions): DBM[];
136
136
  /**
137
137
  * Converts a DBM to storage format, applying compression if configured.
138
138
  *
@@ -164,9 +164,9 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
164
164
  storageRowsToDBMs(rows: ObjectWithId[]): Promise<DBM[]>;
165
165
  private compress;
166
166
  private decompress;
167
- anyToDBM(dbm: undefined, opt?: CommonDaoOptions): Promise<null>;
168
- anyToDBM(dbm?: any, opt?: CommonDaoOptions): Promise<DBM>;
169
- anyToDBMs(rows: DBM[], opt?: CommonDaoOptions): Promise<DBM[]>;
167
+ anyToDBM(dbm: undefined, opt?: CommonDaoOptions): null;
168
+ anyToDBM(dbm?: any, opt?: CommonDaoOptions): DBM;
169
+ anyToDBMs(rows: DBM[], opt?: CommonDaoOptions): DBM[];
170
170
  /**
171
171
  * Returns *converted value* (NOT the same reference).
172
172
  * Does NOT mutate the object.
@@ -183,43 +183,6 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
183
183
  withIds(ids: ID[]): DaoWithIds<CommonDao<BM, DBM, ID>>;
184
184
  withRowsToSave(rows: Unsaved<BM>[]): DaoWithRows<CommonDao<BM, DBM, ID>>;
185
185
  withRowToSave(row: Unsaved<BM>, opt?: DaoWithRowOptions<BM>): DaoWithRow<CommonDao<BM, DBM, ID>>;
186
- /**
187
- * Helper to decompress legacy compressed data when migrating away from auto-compression.
188
- * Use as your `beforeDBMToBM` hook to decompress legacy rows on read.
189
- *
190
- * @example
191
- * const dao = new CommonDao({
192
- * hooks: {
193
- * beforeDBMToBM: CommonDao.decompressLegacyRow,
194
- * }
195
- * })
196
- *
197
- * // Or within an existing hook:
198
- * beforeDBMToBM: async (dbm) => {
199
- * await CommonDao.decompressLegacyRow(dbm)
200
- * // ... other transformations
201
- * return dbm
202
- * }
203
- */
204
- static decompressLegacyRow<T extends ObjectWithId>(this: void, row: T): Promise<T>;
205
- /**
206
- * Temporary helper to migrate from the old `data` compressed property to the new `__compressed` property.
207
- * Use as your `beforeDBMToBM` hook during the migration period.
208
- *
209
- * Migration steps:
210
- * 1. Add `beforeDBMToBM: CommonDao.migrateCompressedDataProperty` to your hooks
211
- * 2. Deploy - old data (with `data` property) will be decompressed on read and recompressed to `__compressed` on write
212
- * 3. Once all data has been naturally rewritten, remove the hook
213
- *
214
- * @example
215
- * const dao = new CommonDao({
216
- * compress: { keys: ['field1', 'field2'] },
217
- * hooks: {
218
- * beforeDBMToBM: CommonDao.migrateCompressedDataProperty,
219
- * }
220
- * })
221
- */
222
- static migrateCompressedDataProperty<T extends ObjectWithId>(row: T): Promise<T>;
223
186
  /**
224
187
  * Load rows (by their ids) from Multiple tables at once.
225
188
  * An optimized way to load data, minimizing DB round-trips.
@@ -83,21 +83,21 @@ export class CommonDao {
83
83
  if (!id)
84
84
  return null;
85
85
  const [dbm] = await this.loadByIds([id], opt);
86
- return await this.dbmToBM(dbm, opt);
86
+ return this.dbmToBM(dbm, opt);
87
87
  }
88
88
  async getByIdAsDBM(id, opt = {}) {
89
89
  if (!id)
90
90
  return null;
91
91
  const [row] = await this.loadByIds([id], opt);
92
- return await this.anyToDBM(row, opt);
92
+ return this.anyToDBM(row, opt);
93
93
  }
94
94
  async getByIds(ids, opt = {}) {
95
95
  const dbms = await this.loadByIds(ids, opt);
96
- return await this.dbmsToBM(dbms, opt);
96
+ return this.dbmsToBM(dbms, opt);
97
97
  }
98
98
  async getByIdsAsDBM(ids, opt = {}) {
99
99
  const rows = await this.loadByIds(ids, opt);
100
- return await this.anyToDBMs(rows);
100
+ return this.anyToDBMs(rows);
101
101
  }
102
102
  // DRY private method
103
103
  async loadByIds(ids, opt = {}) {
@@ -149,7 +149,7 @@ export class CommonDao {
149
149
  const { rows: rawRows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
150
150
  const isPartialQuery = !!q._selectedFieldNames;
151
151
  const rows = isPartialQuery ? rawRows : await this.storageRowsToDBMs(rawRows);
152
- const bms = isPartialQuery ? rows : await this.dbmsToBM(rows, opt);
152
+ const bms = isPartialQuery ? rows : this.dbmsToBM(rows, opt);
153
153
  return {
154
154
  rows: bms,
155
155
  ...queryResult,
@@ -165,7 +165,7 @@ export class CommonDao {
165
165
  const { rows: rawRows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
166
166
  const isPartialQuery = !!q._selectedFieldNames;
167
167
  const rows = isPartialQuery ? rawRows : await this.storageRowsToDBMs(rawRows);
168
- const dbms = isPartialQuery ? rows : await this.anyToDBMs(rows, opt);
168
+ const dbms = isPartialQuery ? rows : this.anyToDBMs(rows, opt);
169
169
  return { rows: dbms, ...queryResult };
170
170
  }
171
171
  async runQueryCount(q, opt = {}) {
@@ -185,7 +185,7 @@ export class CommonDao {
185
185
  return pipeline;
186
186
  opt.skipValidation ??= true;
187
187
  opt.errorMode ||= ErrorMode.SUPPRESS;
188
- return pipeline.map(async dbm => await this.anyToDBM(dbm, opt), { errorMode: opt.errorMode });
188
+ return pipeline.mapSync(dbm => this.anyToDBM(dbm, opt), { errorMode: opt.errorMode });
189
189
  }
190
190
  streamQuery(q, opt = {}) {
191
191
  this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
@@ -199,7 +199,7 @@ export class CommonDao {
199
199
  return pipeline;
200
200
  opt.skipValidation ??= true;
201
201
  opt.errorMode ||= ErrorMode.SUPPRESS;
202
- return pipeline.map(async dbm => await this.dbmToBM(dbm, opt), { errorMode: opt.errorMode });
202
+ return pipeline.mapSync(dbm => this.dbmToBM(dbm, opt), { errorMode: opt.errorMode });
203
203
  }
204
204
  async queryIds(q, opt = {}) {
205
205
  this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
@@ -347,7 +347,7 @@ export class CommonDao {
347
347
  }
348
348
  this.assignIdCreatedUpdated(bm, opt); // mutates
349
349
  _typeCast(bm);
350
- const dbm = await this.bmToDBM(bm, opt); // validates BM
350
+ const dbm = this.bmToDBM(bm, opt); // validates BM
351
351
  this.cfg.hooks.beforeSave?.(dbm);
352
352
  const table = opt.table || this.cfg.table;
353
353
  const saveOptions = this.prepareSaveOptions(opt);
@@ -361,7 +361,7 @@ export class CommonDao {
361
361
  async saveAsDBM(dbm, opt = {}) {
362
362
  this.requireWriteAccess();
363
363
  this.assignIdCreatedUpdated(dbm, opt); // mutates
364
- const validDbm = await this.anyToDBM(dbm, opt);
364
+ const validDbm = this.anyToDBM(dbm, opt);
365
365
  this.cfg.hooks.beforeSave?.(validDbm);
366
366
  const table = opt.table || this.cfg.table;
367
367
  const saveOptions = this.prepareSaveOptions(opt);
@@ -377,7 +377,7 @@ export class CommonDao {
377
377
  return [];
378
378
  this.requireWriteAccess();
379
379
  bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt));
380
- const dbms = await this.bmsToDBM(bms, opt);
380
+ const dbms = this.bmsToDBM(bms, opt);
381
381
  if (this.cfg.hooks.beforeSave) {
382
382
  dbms.forEach(dbm => this.cfg.hooks.beforeSave(dbm));
383
383
  }
@@ -395,7 +395,7 @@ export class CommonDao {
395
395
  return [];
396
396
  this.requireWriteAccess();
397
397
  dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt));
398
- const validDbms = await this.anyToDBMs(dbms, opt);
398
+ const validDbms = this.anyToDBMs(dbms, opt);
399
399
  if (this.cfg.hooks.beforeSave) {
400
400
  validDbms.forEach(dbm => this.cfg.hooks.beforeSave(dbm));
401
401
  }
@@ -449,7 +449,7 @@ export class CommonDao {
449
449
  await p
450
450
  .map(async bm => {
451
451
  this.assignIdCreatedUpdated(bm, opt);
452
- const dbm = await this.bmToDBM(bm, opt);
452
+ const dbm = this.bmToDBM(bm, opt);
453
453
  beforeSave?.(dbm);
454
454
  return await this.dbmToStorageRow(dbm);
455
455
  }, { errorMode })
@@ -560,32 +560,31 @@ export class CommonDao {
560
560
  const { table } = this.cfg;
561
561
  return await this.cfg.db.incrementBatch(table, prop, incrementMap);
562
562
  }
563
- async dbmToBM(_dbm, opt = {}) {
563
+ dbmToBM(_dbm, opt = {}) {
564
564
  if (!_dbm)
565
565
  return null;
566
566
  // optimization: no need to run full joi DBM validation, cause BM validation will be run
567
567
  // const dbm = this.anyToDBM(_dbm, opt)
568
568
  const dbm = { ..._dbm, ...this.cfg.hooks.parseNaturalId(_dbm.id) };
569
569
  // DBM > BM
570
- const bm = ((await this.cfg.hooks.beforeDBMToBM?.(dbm)) || dbm);
570
+ const bm = (this.cfg.hooks.beforeDBMToBM?.(dbm) || dbm);
571
571
  // Validate/convert BM
572
572
  return this.validateAndConvert(bm, 'load', opt);
573
573
  }
574
- async dbmsToBM(dbms, opt = {}) {
575
- return await pMap(dbms, async dbm => await this.dbmToBM(dbm, opt));
574
+ dbmsToBM(dbms, opt = {}) {
575
+ return dbms.map(dbm => this.dbmToBM(dbm, opt));
576
576
  }
577
- async bmToDBM(bm, opt) {
577
+ bmToDBM(bm, opt) {
578
578
  if (bm === undefined)
579
579
  return null;
580
580
  // bm gets assigned to the new reference
581
581
  bm = this.validateAndConvert(bm, 'save', opt);
582
582
  // BM > DBM
583
- const dbm = ((await this.cfg.hooks.beforeBMToDBM?.(bm)) || bm);
583
+ const dbm = (this.cfg.hooks.beforeBMToDBM?.(bm) || bm);
584
584
  return dbm;
585
585
  }
586
- async bmsToDBM(bms, opt = {}) {
587
- // try/catch?
588
- return await pMap(bms, async bm => await this.bmToDBM(bm, opt));
586
+ bmsToDBM(bms, opt = {}) {
587
+ return bms.map(bm => this.bmToDBM(bm, opt));
589
588
  }
590
589
  // STORAGE LAYER (compression/decompression at DB boundary)
591
590
  // These methods convert between DBM (logical model) and storage format (physical, possibly compressed).
@@ -661,6 +660,7 @@ export class CommonDao {
661
660
  if (!Buffer.isBuffer(dbm.__compressed))
662
661
  return; // No compressed data
663
662
  try {
663
+ // todo: stop supporting Inflate when we are sure that we have migrated everything to zstd
664
664
  const bufferString = await decompressZstdOrInflateToString(dbm.__compressed);
665
665
  const properties = JSON.parse(bufferString);
666
666
  dbm.__compressed = undefined;
@@ -668,7 +668,7 @@ export class CommonDao {
668
668
  }
669
669
  catch { }
670
670
  }
671
- async anyToDBM(dbm, _opt = {}) {
671
+ anyToDBM(dbm, _opt = {}) {
672
672
  if (!dbm)
673
673
  return null;
674
674
  // this shouldn't be happening on load! but should on save!
@@ -678,8 +678,8 @@ export class CommonDao {
678
678
  // return this.validateAndConvert(dbm, this.cfg.dbmSchema, DBModelType.DBM, opt)
679
679
  return dbm;
680
680
  }
681
- async anyToDBMs(rows, opt = {}) {
682
- return await pMap(rows, async entity => await this.anyToDBM(entity, opt));
681
+ anyToDBMs(rows, opt = {}) {
682
+ return rows.map(entity => this.anyToDBM(entity, opt));
683
683
  }
684
684
  /**
685
685
  * Returns *converted value* (NOT the same reference).
@@ -751,73 +751,6 @@ export class CommonDao {
751
751
  opt: opt,
752
752
  };
753
753
  }
754
- /**
755
- * Helper to decompress legacy compressed data when migrating away from auto-compression.
756
- * Use as your `beforeDBMToBM` hook to decompress legacy rows on read.
757
- *
758
- * @example
759
- * const dao = new CommonDao({
760
- * hooks: {
761
- * beforeDBMToBM: CommonDao.decompressLegacyRow,
762
- * }
763
- * })
764
- *
765
- * // Or within an existing hook:
766
- * beforeDBMToBM: async (dbm) => {
767
- * await CommonDao.decompressLegacyRow(dbm)
768
- * // ... other transformations
769
- * return dbm
770
- * }
771
- */
772
- static async decompressLegacyRow(row) {
773
- // Check both __compressed (current) and data (legacy) for backward compatibility
774
- const compressed = row.__compressed ?? row.data;
775
- if (!Buffer.isBuffer(compressed))
776
- return row;
777
- try {
778
- const bufferString = await decompressZstdOrInflateToString(compressed);
779
- const properties = JSON.parse(bufferString);
780
- row.__compressed = undefined;
781
- row.data = undefined;
782
- Object.assign(row, properties);
783
- }
784
- catch {
785
- // Decompression failed - field is not compressed, leave as-is
786
- }
787
- return row;
788
- }
789
- /**
790
- * Temporary helper to migrate from the old `data` compressed property to the new `__compressed` property.
791
- * Use as your `beforeDBMToBM` hook during the migration period.
792
- *
793
- * Migration steps:
794
- * 1. Add `beforeDBMToBM: CommonDao.migrateCompressedDataProperty` to your hooks
795
- * 2. Deploy - old data (with `data` property) will be decompressed on read and recompressed to `__compressed` on write
796
- * 3. Once all data has been naturally rewritten, remove the hook
797
- *
798
- * @example
799
- * const dao = new CommonDao({
800
- * compress: { keys: ['field1', 'field2'] },
801
- * hooks: {
802
- * beforeDBMToBM: CommonDao.migrateCompressedDataProperty,
803
- * }
804
- * })
805
- */
806
- static async migrateCompressedDataProperty(row) {
807
- const data = row.data;
808
- if (!Buffer.isBuffer(data))
809
- return row;
810
- try {
811
- const bufferString = await decompressZstdOrInflateToString(data);
812
- const properties = JSON.parse(bufferString);
813
- row.data = undefined;
814
- Object.assign(row, properties);
815
- }
816
- catch {
817
- // Decompression failed - data field is not compressed, leave as-is
818
- }
819
- return row;
820
- }
821
754
  /**
822
755
  * Load rows (by their ids) from Multiple tables at once.
823
756
  * An optimized way to load data, minimizing DB round-trips.
@@ -881,7 +814,7 @@ export class CommonDao {
881
814
  const row = dbmByTableById[table][input.id];
882
815
  // Decompress before converting to BM
883
816
  const dbm = row ? await dao.storageRowToDBM(row) : undefined;
884
- bmsByProp[prop] = (await dao.dbmToBM(dbm, opt)) || null;
817
+ bmsByProp[prop] = dao.dbmToBM(dbm, opt) || null;
885
818
  }
886
819
  else {
887
820
  // Plural
@@ -890,7 +823,7 @@ export class CommonDao {
890
823
  const rows = input.ids.map(id => dbmByTableById[table][id]).filter(_isTruthy);
891
824
  // Decompress before converting to BM
892
825
  const dbms = await dao.storageRowsToDBMs(rows);
893
- bmsByProp[prop] = await dao.dbmsToBM(dbms, opt);
826
+ bmsByProp[prop] = dao.dbmsToBM(dbms, opt);
894
827
  }
895
828
  });
896
829
  return bmsByProp;
@@ -941,7 +874,7 @@ export class CommonDao {
941
874
  }
942
875
  }
943
876
  dao.assignIdCreatedUpdated(row, opt);
944
- const dbm = await dao.bmToDBM(row, opt);
877
+ const dbm = dao.bmToDBM(row, opt);
945
878
  dao.cfg.hooks.beforeSave?.(dbm);
946
879
  const storageRow = await dao.dbmToStorageRow(dbm);
947
880
  dbmsByTable[table].push(storageRow);
@@ -949,7 +882,7 @@ export class CommonDao {
949
882
  else {
950
883
  // Plural
951
884
  input.rows.forEach(bm => dao.assignIdCreatedUpdated(bm, opt));
952
- const dbms = await dao.bmsToDBM(input.rows, opt);
885
+ const dbms = dao.bmsToDBM(input.rows, opt);
953
886
  if (dao.cfg.hooks.beforeSave) {
954
887
  dbms.forEach(dbm => dao.cfg.hooks.beforeSave(dbm));
955
888
  }
@@ -31,8 +31,8 @@ export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntit
31
31
  * - patch, patchAsDBM
32
32
  */
33
33
  beforeCreate: (bm: Partial<BM>) => Partial<BM>;
34
- beforeDBMToBM: (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>;
35
- beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>;
34
+ beforeDBMToBM: (dbm: DBM) => Partial<BM>;
35
+ beforeBMToDBM: (bm: BM) => Partial<DBM>;
36
36
  /**
37
37
  * Allows to access the DBM just after it has been loaded from the DB.
38
38
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/db-lib",
3
3
  "type": "module",
4
- "version": "10.45.2",
4
+ "version": "10.46.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@naturalcycles/nodejs-lib": "^15"
@@ -40,8 +40,8 @@ export interface CommonDaoHooks<
40
40
  */
41
41
  beforeCreate: (bm: Partial<BM>) => Partial<BM>
42
42
 
43
- beforeDBMToBM: (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>
44
- beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>
43
+ beforeDBMToBM: (dbm: DBM) => Partial<BM>
44
+ beforeBMToDBM: (bm: BM) => Partial<DBM>
45
45
 
46
46
  /**
47
47
  * Allows to access the DBM just after it has been loaded from the DB.
@@ -131,23 +131,23 @@ export class CommonDao<
131
131
  async getById(id?: ID | null, opt: CommonDaoReadOptions = {}): Promise<BM | null> {
132
132
  if (!id) return null
133
133
  const [dbm] = await this.loadByIds([id], opt)
134
- return await this.dbmToBM(dbm, opt)
134
+ return this.dbmToBM(dbm, opt)
135
135
  }
136
136
 
137
137
  async getByIdAsDBM(id?: ID | null, opt: CommonDaoReadOptions = {}): Promise<DBM | null> {
138
138
  if (!id) return null
139
139
  const [row] = await this.loadByIds([id], opt)
140
- return await this.anyToDBM(row, opt)
140
+ return this.anyToDBM(row, opt)
141
141
  }
142
142
 
143
143
  async getByIds(ids: ID[], opt: CommonDaoReadOptions = {}): Promise<BM[]> {
144
144
  const dbms = await this.loadByIds(ids, opt)
145
- return await this.dbmsToBM(dbms, opt)
145
+ return this.dbmsToBM(dbms, opt)
146
146
  }
147
147
 
148
148
  async getByIdsAsDBM(ids: ID[], opt: CommonDaoReadOptions = {}): Promise<DBM[]> {
149
149
  const rows = await this.loadByIds(ids, opt)
150
- return await this.anyToDBMs(rows)
150
+ return this.anyToDBMs(rows)
151
151
  }
152
152
 
153
153
  // DRY private method
@@ -217,7 +217,7 @@ export class CommonDao<
217
217
  const { rows: rawRows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
218
218
  const isPartialQuery = !!q._selectedFieldNames
219
219
  const rows = isPartialQuery ? rawRows : await this.storageRowsToDBMs(rawRows)
220
- const bms = isPartialQuery ? (rows as any[]) : await this.dbmsToBM(rows, opt)
220
+ const bms = isPartialQuery ? (rows as any[]) : this.dbmsToBM(rows, opt)
221
221
  return {
222
222
  rows: bms,
223
223
  ...queryResult,
@@ -238,7 +238,7 @@ export class CommonDao<
238
238
  const { rows: rawRows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
239
239
  const isPartialQuery = !!q._selectedFieldNames
240
240
  const rows = isPartialQuery ? rawRows : await this.storageRowsToDBMs(rawRows)
241
- const dbms = isPartialQuery ? rows : await this.anyToDBMs(rows, opt)
241
+ const dbms = isPartialQuery ? rows : this.anyToDBMs(rows, opt)
242
242
  return { rows: dbms, ...queryResult }
243
243
  }
244
244
 
@@ -263,7 +263,7 @@ export class CommonDao<
263
263
  opt.skipValidation ??= true
264
264
  opt.errorMode ||= ErrorMode.SUPPRESS
265
265
 
266
- return pipeline.map(async dbm => await this.anyToDBM(dbm, opt), { errorMode: opt.errorMode })
266
+ return pipeline.mapSync(dbm => this.anyToDBM(dbm, opt), { errorMode: opt.errorMode })
267
267
  }
268
268
 
269
269
  streamQuery(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<BM> = {}): Pipeline<BM> {
@@ -281,7 +281,7 @@ export class CommonDao<
281
281
  opt.skipValidation ??= true
282
282
  opt.errorMode ||= ErrorMode.SUPPRESS
283
283
 
284
- return pipeline.map(async dbm => await this.dbmToBM(dbm, opt), { errorMode: opt.errorMode })
284
+ return pipeline.mapSync(dbm => this.dbmToBM(dbm, opt), { errorMode: opt.errorMode })
285
285
  }
286
286
 
287
287
  async queryIds(q: DBQuery<DBM>, opt: CommonDaoReadOptions = {}): Promise<ID[]> {
@@ -466,7 +466,7 @@ export class CommonDao<
466
466
 
467
467
  this.assignIdCreatedUpdated(bm, opt) // mutates
468
468
  _typeCast<BM>(bm)
469
- const dbm = await this.bmToDBM(bm, opt) // validates BM
469
+ const dbm = this.bmToDBM(bm, opt) // validates BM
470
470
  this.cfg.hooks!.beforeSave?.(dbm)
471
471
  const table = opt.table || this.cfg.table
472
472
  const saveOptions = this.prepareSaveOptions(opt)
@@ -484,7 +484,7 @@ export class CommonDao<
484
484
  async saveAsDBM(dbm: Unsaved<DBM>, opt: CommonDaoSaveOptions<BM, DBM> = {}): Promise<DBM> {
485
485
  this.requireWriteAccess()
486
486
  this.assignIdCreatedUpdated(dbm, opt) // mutates
487
- const validDbm = await this.anyToDBM(dbm, opt)
487
+ const validDbm = this.anyToDBM(dbm, opt)
488
488
  this.cfg.hooks!.beforeSave?.(validDbm)
489
489
  const table = opt.table || this.cfg.table
490
490
  const saveOptions = this.prepareSaveOptions(opt)
@@ -503,7 +503,7 @@ export class CommonDao<
503
503
  if (!bms.length) return []
504
504
  this.requireWriteAccess()
505
505
  bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt))
506
- const dbms = await this.bmsToDBM(bms as BM[], opt)
506
+ const dbms = this.bmsToDBM(bms as BM[], opt)
507
507
  if (this.cfg.hooks!.beforeSave) {
508
508
  dbms.forEach(dbm => this.cfg.hooks!.beforeSave!(dbm))
509
509
  }
@@ -527,7 +527,7 @@ export class CommonDao<
527
527
  if (!dbms.length) return []
528
528
  this.requireWriteAccess()
529
529
  dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt))
530
- const validDbms = await this.anyToDBMs(dbms as DBM[], opt)
530
+ const validDbms = this.anyToDBMs(dbms as DBM[], opt)
531
531
  if (this.cfg.hooks!.beforeSave) {
532
532
  validDbms.forEach(dbm => this.cfg.hooks!.beforeSave!(dbm))
533
533
  }
@@ -600,7 +600,7 @@ export class CommonDao<
600
600
  .map(
601
601
  async bm => {
602
602
  this.assignIdCreatedUpdated(bm, opt)
603
- const dbm = await this.bmToDBM(bm, opt)
603
+ const dbm = this.bmToDBM(bm, opt)
604
604
  beforeSave?.(dbm)
605
605
  return await this.dbmToStorageRow(dbm)
606
606
  },
@@ -739,9 +739,9 @@ export class CommonDao<
739
739
 
740
740
  // CONVERSIONS
741
741
 
742
- async dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): Promise<null>
743
- async dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): Promise<BM>
744
- async dbmToBM(_dbm?: DBM, opt: CommonDaoOptions = {}): Promise<BM | null> {
742
+ dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): null
743
+ dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): BM
744
+ dbmToBM(_dbm?: DBM, opt: CommonDaoOptions = {}): BM | null {
745
745
  if (!_dbm) return null
746
746
 
747
747
  // optimization: no need to run full joi DBM validation, cause BM validation will be run
@@ -749,37 +749,36 @@ export class CommonDao<
749
749
  const dbm: DBM = { ..._dbm, ...this.cfg.hooks!.parseNaturalId!(_dbm.id as ID) }
750
750
 
751
751
  // DBM > BM
752
- const bm = ((await this.cfg.hooks!.beforeDBMToBM?.(dbm)) || dbm) as Partial<BM>
752
+ const bm = (this.cfg.hooks!.beforeDBMToBM?.(dbm) || dbm) as Partial<BM>
753
753
 
754
754
  // Validate/convert BM
755
755
  return this.validateAndConvert(bm, 'load', opt)
756
756
  }
757
757
 
758
- async dbmsToBM(dbms: DBM[], opt: CommonDaoOptions = {}): Promise<BM[]> {
759
- return await pMap(dbms, async dbm => await this.dbmToBM(dbm, opt))
758
+ dbmsToBM(dbms: DBM[], opt: CommonDaoOptions = {}): BM[] {
759
+ return dbms.map(dbm => this.dbmToBM(dbm, opt))
760
760
  }
761
761
 
762
762
  /**
763
763
  * Mutates object with properties: id, created, updated.
764
764
  * Returns DBM (new reference).
765
765
  */
766
- async bmToDBM(bm: undefined, opt?: CommonDaoOptions): Promise<null>
767
- async bmToDBM(bm?: BM, opt?: CommonDaoOptions): Promise<DBM>
768
- async bmToDBM(bm?: BM, opt?: CommonDaoOptions): Promise<DBM | null> {
766
+ bmToDBM(bm: undefined, opt?: CommonDaoOptions): null
767
+ bmToDBM(bm?: BM, opt?: CommonDaoOptions): DBM
768
+ bmToDBM(bm?: BM, opt?: CommonDaoOptions): DBM | null {
769
769
  if (bm === undefined) return null
770
770
 
771
771
  // bm gets assigned to the new reference
772
772
  bm = this.validateAndConvert(bm, 'save', opt)
773
773
 
774
774
  // BM > DBM
775
- const dbm = ((await this.cfg.hooks!.beforeBMToDBM?.(bm)) || bm) as DBM
775
+ const dbm = (this.cfg.hooks!.beforeBMToDBM?.(bm) || bm) as DBM
776
776
 
777
777
  return dbm
778
778
  }
779
779
 
780
- async bmsToDBM(bms: BM[], opt: CommonDaoOptions = {}): Promise<DBM[]> {
781
- // try/catch?
782
- return await pMap(bms, async bm => await this.bmToDBM(bm, opt))
780
+ bmsToDBM(bms: BM[], opt: CommonDaoOptions = {}): DBM[] {
781
+ return bms.map(bm => this.bmToDBM(bm, opt))
783
782
  }
784
783
 
785
784
  // STORAGE LAYER (compression/decompression at DB boundary)
@@ -858,6 +857,7 @@ export class CommonDao<
858
857
  if (!Buffer.isBuffer(dbm.__compressed)) return // No compressed data
859
858
 
860
859
  try {
860
+ // todo: stop supporting Inflate when we are sure that we have migrated everything to zstd
861
861
  const bufferString = await decompressZstdOrInflateToString(dbm.__compressed)
862
862
  const properties = JSON.parse(bufferString)
863
863
  dbm.__compressed = undefined
@@ -865,9 +865,9 @@ export class CommonDao<
865
865
  } catch {}
866
866
  }
867
867
 
868
- async anyToDBM(dbm: undefined, opt?: CommonDaoOptions): Promise<null>
869
- async anyToDBM(dbm?: any, opt?: CommonDaoOptions): Promise<DBM>
870
- async anyToDBM(dbm?: DBM, _opt: CommonDaoOptions = {}): Promise<DBM | null> {
868
+ anyToDBM(dbm: undefined, opt?: CommonDaoOptions): null
869
+ anyToDBM(dbm?: any, opt?: CommonDaoOptions): DBM
870
+ anyToDBM(dbm?: DBM, _opt: CommonDaoOptions = {}): DBM | null {
871
871
  if (!dbm) return null
872
872
 
873
873
  // this shouldn't be happening on load! but should on save!
@@ -880,8 +880,8 @@ export class CommonDao<
880
880
  return dbm
881
881
  }
882
882
 
883
- async anyToDBMs(rows: DBM[], opt: CommonDaoOptions = {}): Promise<DBM[]> {
884
- return await pMap(rows, async entity => await this.anyToDBM(entity, opt))
883
+ anyToDBMs(rows: DBM[], opt: CommonDaoOptions = {}): DBM[] {
884
+ return rows.map(entity => this.anyToDBM(entity, opt))
885
885
  }
886
886
 
887
887
  /**
@@ -971,75 +971,6 @@ export class CommonDao<
971
971
  }
972
972
  }
973
973
 
974
- /**
975
- * Helper to decompress legacy compressed data when migrating away from auto-compression.
976
- * Use as your `beforeDBMToBM` hook to decompress legacy rows on read.
977
- *
978
- * @example
979
- * const dao = new CommonDao({
980
- * hooks: {
981
- * beforeDBMToBM: CommonDao.decompressLegacyRow,
982
- * }
983
- * })
984
- *
985
- * // Or within an existing hook:
986
- * beforeDBMToBM: async (dbm) => {
987
- * await CommonDao.decompressLegacyRow(dbm)
988
- * // ... other transformations
989
- * return dbm
990
- * }
991
- */
992
- static async decompressLegacyRow<T extends ObjectWithId>(this: void, row: T): Promise<T> {
993
- // Check both __compressed (current) and data (legacy) for backward compatibility
994
- const compressed = (row as any).__compressed ?? (row as any).data
995
- if (!Buffer.isBuffer(compressed)) return row
996
-
997
- try {
998
- const bufferString = await decompressZstdOrInflateToString(compressed)
999
- const properties = JSON.parse(bufferString)
1000
- ;(row as any).__compressed = undefined
1001
- ;(row as any).data = undefined
1002
- Object.assign(row, properties)
1003
- } catch {
1004
- // Decompression failed - field is not compressed, leave as-is
1005
- }
1006
-
1007
- return row
1008
- }
1009
-
1010
- /**
1011
- * Temporary helper to migrate from the old `data` compressed property to the new `__compressed` property.
1012
- * Use as your `beforeDBMToBM` hook during the migration period.
1013
- *
1014
- * Migration steps:
1015
- * 1. Add `beforeDBMToBM: CommonDao.migrateCompressedDataProperty` to your hooks
1016
- * 2. Deploy - old data (with `data` property) will be decompressed on read and recompressed to `__compressed` on write
1017
- * 3. Once all data has been naturally rewritten, remove the hook
1018
- *
1019
- * @example
1020
- * const dao = new CommonDao({
1021
- * compress: { keys: ['field1', 'field2'] },
1022
- * hooks: {
1023
- * beforeDBMToBM: CommonDao.migrateCompressedDataProperty,
1024
- * }
1025
- * })
1026
- */
1027
- static async migrateCompressedDataProperty<T extends ObjectWithId>(row: T): Promise<T> {
1028
- const data = (row as any).data
1029
- if (!Buffer.isBuffer(data)) return row
1030
-
1031
- try {
1032
- const bufferString = await decompressZstdOrInflateToString(data)
1033
- const properties = JSON.parse(bufferString)
1034
- ;(row as any).data = undefined
1035
- Object.assign(row, properties)
1036
- } catch {
1037
- // Decompression failed - data field is not compressed, leave as-is
1038
- }
1039
-
1040
- return row
1041
- }
1042
-
1043
974
  /**
1044
975
  * Load rows (by their ids) from Multiple tables at once.
1045
976
  * An optimized way to load data, minimizing DB round-trips.
@@ -1128,7 +1059,7 @@ export class CommonDao<
1128
1059
  const row = dbmByTableById[table]![input.id]
1129
1060
  // Decompress before converting to BM
1130
1061
  const dbm = row ? await dao.storageRowToDBM(row) : undefined
1131
- bmsByProp[prop] = (await dao.dbmToBM(dbm, opt)) || null
1062
+ bmsByProp[prop] = dao.dbmToBM(dbm, opt) || null
1132
1063
  } else {
1133
1064
  // Plural
1134
1065
  // We apply filtering, to be able to support multiple input props fetching from the same table.
@@ -1136,7 +1067,7 @@ export class CommonDao<
1136
1067
  const rows = input.ids.map(id => dbmByTableById[table]![id]).filter(_isTruthy)
1137
1068
  // Decompress before converting to BM
1138
1069
  const dbms = await dao.storageRowsToDBMs(rows)
1139
- bmsByProp[prop] = await dao.dbmsToBM(dbms, opt)
1070
+ bmsByProp[prop] = dao.dbmsToBM(dbms, opt)
1140
1071
  }
1141
1072
  })
1142
1073
 
@@ -1198,14 +1129,14 @@ export class CommonDao<
1198
1129
  }
1199
1130
 
1200
1131
  dao.assignIdCreatedUpdated(row, opt)
1201
- const dbm = await dao.bmToDBM(row, opt)
1132
+ const dbm = dao.bmToDBM(row, opt)
1202
1133
  dao.cfg.hooks!.beforeSave?.(dbm)
1203
1134
  const storageRow = await dao.dbmToStorageRow(dbm)
1204
1135
  dbmsByTable[table].push(storageRow)
1205
1136
  } else {
1206
1137
  // Plural
1207
1138
  input.rows.forEach(bm => dao.assignIdCreatedUpdated(bm, opt))
1208
- const dbms = await dao.bmsToDBM(input.rows, opt)
1139
+ const dbms = dao.bmsToDBM(input.rows, opt)
1209
1140
  if (dao.cfg.hooks!.beforeSave) {
1210
1141
  dbms.forEach(dbm => dao.cfg.hooks!.beforeSave!(dbm))
1211
1142
  }