@naturalcycles/db-lib 9.16.0 → 9.18.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.
@@ -11,19 +11,17 @@ import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoPatchB
11
11
  * BM = Backend model (optimized for API access)
12
12
  * TM = Transport model (optimized to be sent over the wire)
13
13
  */
14
- export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
15
- cfg: CommonDaoCfg<BM, DBM>;
16
- constructor(cfg: CommonDaoCfg<BM, DBM>);
14
+ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID = BM['id']> {
15
+ cfg: CommonDaoCfg<BM, DBM, ID>;
16
+ constructor(cfg: CommonDaoCfg<BM, DBM, ID>);
17
17
  create(part?: Partial<BM>, opt?: CommonDaoOptions): BM;
18
- getById(id: undefined | null, opt?: CommonDaoOptions): Promise<null>;
19
- getById(id?: string | null, opt?: CommonDaoOptions): Promise<BM | null>;
20
- getByIdOrEmpty(id: string, part?: Partial<BM>, opt?: CommonDaoOptions): Promise<BM>;
21
- getByIdAsDBM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>;
22
- getByIdAsDBM(id?: string | null, opt?: CommonDaoOptions): Promise<DBM | null>;
23
- getByIds(ids: string[], opt?: CommonDaoOptions): Promise<BM[]>;
24
- getByIdsAsDBM(ids: string[], opt?: CommonDaoOptions): Promise<DBM[]>;
25
- requireById(id: string, opt?: CommonDaoOptions): Promise<BM>;
26
- requireByIdAsDBM(id: string, opt?: CommonDaoOptions): Promise<DBM>;
18
+ getById(id?: ID | null, opt?: CommonDaoOptions): Promise<BM | null>;
19
+ getByIdOrEmpty(id: ID, part?: Partial<BM>, opt?: CommonDaoOptions): Promise<BM>;
20
+ getByIdAsDBM(id?: ID | null, opt?: CommonDaoOptions): Promise<DBM | null>;
21
+ getByIds(ids: ID[], opt?: CommonDaoOptions): Promise<BM[]>;
22
+ getByIdsAsDBM(ids: ID[], opt?: CommonDaoOptions): Promise<DBM[]>;
23
+ requireById(id: ID, opt?: CommonDaoOptions): Promise<BM>;
24
+ requireByIdAsDBM(id: ID, opt?: CommonDaoOptions): Promise<DBM>;
27
25
  private throwRequiredError;
28
26
  /**
29
27
  * Throws if readOnly is true
@@ -40,7 +38,7 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
40
38
  /**
41
39
  * Pass `table` to override table
42
40
  */
43
- query(table?: string): RunnableDBQuery<BM, DBM>;
41
+ query(table?: string): RunnableDBQuery<BM, DBM, ID>;
44
42
  runQuery(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<BM[]>;
45
43
  runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<T[]>;
46
44
  /**
@@ -69,9 +67,9 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
69
67
  * You can do `.pipe(transformNoOp)` to make it "valid again".
70
68
  */
71
69
  streamQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<BM>): ReadableTyped<BM>;
72
- queryIds(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<string[]>;
73
- streamQueryIds(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<string>): ReadableTyped<string>;
74
- streamQueryIdsForEach(q: DBQuery<DBM>, mapper: AsyncMapper<string, void>, opt?: CommonDaoStreamForEachOptions<string>): Promise<void>;
70
+ queryIds(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<ID[]>;
71
+ streamQueryIds(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<ID>): ReadableTyped<ID>;
72
+ streamQueryIdsForEach(q: DBQuery<DBM>, mapper: AsyncMapper<ID, void>, opt?: CommonDaoStreamForEachOptions<ID>): Promise<void>;
75
73
  /**
76
74
  * Mutates!
77
75
  * "Returns", just to have a type of "Saved"
@@ -86,11 +84,11 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
86
84
  * 2. Applies the patch on top of loaded data.
87
85
  * 3. Saves (as fast as possible since the read) with the Patch applied, but only if the data has changed.
88
86
  */
89
- patchById(id: string, patch: Partial<BM>, opt?: CommonDaoPatchByIdOptions<DBM>): Promise<BM>;
87
+ patchById(id: ID, patch: Partial<BM>, opt?: CommonDaoPatchByIdOptions<DBM>): Promise<BM>;
90
88
  /**
91
89
  * Like patchById, but runs all operations within a Transaction.
92
90
  */
93
- patchByIdInTransaction(id: string, patch: Partial<BM>, opt?: CommonDaoPatchByIdOptions<DBM>): Promise<BM>;
91
+ patchByIdInTransaction(id: ID, patch: Partial<BM>, opt?: CommonDaoPatchByIdOptions<DBM>): Promise<BM>;
94
92
  /**
95
93
  * Same as patchById, but takes the whole object as input.
96
94
  * This "whole object" is mutated with the patch and returned.
@@ -119,16 +117,16 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
119
117
  /**
120
118
  * @returns number of deleted items
121
119
  */
122
- deleteById(id?: string | null, opt?: CommonDaoOptions): Promise<number>;
123
- deleteByIds(ids: string[], opt?: CommonDaoOptions): Promise<number>;
120
+ deleteById(id?: ID | null, opt?: CommonDaoOptions): Promise<number>;
121
+ deleteByIds(ids: ID[], opt?: CommonDaoOptions): Promise<number>;
124
122
  /**
125
123
  * Pass `chunkSize: number` (e.g 500) option to use Streaming: it will Stream the query, chunk by 500, and execute
126
124
  * `deleteByIds` for each chunk concurrently (infinite concurrency).
127
125
  * This is expected to be more memory-efficient way of deleting large number of rows.
128
126
  */
129
127
  deleteByQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamDeleteOptions<DBM>): Promise<number>;
130
- updateById(id: string, patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
131
- updateByIds(ids: string[], patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
128
+ updateById(id: ID, patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
129
+ updateByIds(ids: ID[], patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
132
130
  updateByQuery(q: DBQuery<DBM>, patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
133
131
  dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): Promise<undefined>;
134
132
  dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): Promise<BM>;
@@ -184,8 +182,8 @@ export declare class CommonDaoTransaction {
184
182
  * Perform a graceful rollback without throwing/re-throwing any error.
185
183
  */
186
184
  rollback(): Promise<void>;
187
- getById<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, id?: string | null, opt?: CommonDaoOptions): Promise<BM | null>;
188
- getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, ids: string[], opt?: CommonDaoOptions): Promise<BM[]>;
185
+ getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, id?: ID | null, opt?: CommonDaoOptions): Promise<BM | null>;
186
+ getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, ids: ID[], opt?: CommonDaoOptions): Promise<BM[]>;
189
187
  save<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<BM>;
190
188
  saveBatch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, bms: Unsaved<BM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM[]>;
191
189
  /**
@@ -195,7 +193,7 @@ export declare class CommonDaoTransaction {
195
193
  *
196
194
  * So, this method is a rather simple convenience "Object.assign and then save".
197
195
  */
198
- patch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, bm: BM, patch: Partial<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<BM>;
199
- deleteById(dao: CommonDao<any>, id?: string | null, opt?: CommonDaoOptions): Promise<number>;
200
- deleteByIds(dao: CommonDao<any>, ids: string[], opt?: CommonDaoOptions): Promise<number>;
196
+ patch<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, bm: BM, patch: Partial<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<BM>;
197
+ deleteById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, id?: ID | null, opt?: CommonDaoOptions): Promise<number>;
198
+ deleteByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, ids: ID[], opt?: CommonDaoOptions): Promise<number>;
201
199
  }
@@ -53,6 +53,10 @@ class CommonDao {
53
53
  this.assignIdCreatedUpdated(bm, opt);
54
54
  return this.validateAndConvert(bm, this.cfg.bmSchema, undefined, opt);
55
55
  }
56
+ // GET
57
+ // overrides are disabled now, as they obfuscate errors when ID branded type is used
58
+ // async getById(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
59
+ // async getById(id?: ID | null, opt?: CommonDaoOptions): Promise<BM | null>
56
60
  async getById(id, opt = {}) {
57
61
  if (!id)
58
62
  return null;
@@ -73,6 +77,8 @@ class CommonDao {
73
77
  return bm;
74
78
  return this.create({ ...part, id }, opt);
75
79
  }
80
+ // async getByIdAsDBM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
81
+ // async getByIdAsDBM(id?: ID | null, opt?: CommonDaoOptions): Promise<DBM | null>
76
82
  async getByIdAsDBM(id, opt = {}) {
77
83
  if (!id)
78
84
  return null;
@@ -465,7 +471,8 @@ class CommonDao {
465
471
  obj.updated = opt.preserveUpdatedCreated && obj.updated ? obj.updated : now;
466
472
  }
467
473
  if (this.cfg.generateId) {
468
- obj.id ||= this.cfg.hooks.createNaturalId?.(obj) || this.cfg.hooks.createRandomId();
474
+ obj.id ||= (this.cfg.hooks.createNaturalId?.(obj) ||
475
+ this.cfg.hooks.createRandomId());
469
476
  }
470
477
  return obj;
471
478
  }
@@ -2,24 +2,24 @@ import { BaseDBEntity, CommonLogger, ErrorMode, Promisable, ZodError, ZodSchema
2
2
  import { AjvSchema, AjvValidationError, JoiValidationError, ObjectSchema, TransformLogProgressOptions, TransformMapOptions } from '@naturalcycles/nodejs-lib';
3
3
  import { CommonDB } from '../common.db';
4
4
  import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model';
5
- export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntity> {
5
+ export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']> {
6
6
  /**
7
7
  * Allows to override the id generation function.
8
8
  * By default it uses `stringId` from nodejs-lib
9
9
  * (which uses lowercase alphanumberic alphabet and the size of 16).
10
10
  */
11
- createRandomId: () => string;
11
+ createRandomId: () => ID;
12
12
  /**
13
13
  * createNaturalId hook is called (tried) first.
14
14
  * If it doesn't exist - createRandomId is called.
15
15
  */
16
- createNaturalId: (obj: DBM | BM) => string;
16
+ createNaturalId: (obj: DBM | BM) => ID;
17
17
  /**
18
18
  * It's a counter-part of `createNaturalId`.
19
19
  * Allows to provide a parser function to parse "natural id" into
20
20
  * DBM components (e.g accountId and some other property that is part of the id).
21
21
  */
22
- parseNaturalId: (id: string) => Partial<DBM>;
22
+ parseNaturalId: (id: ID) => Partial<DBM>;
23
23
  /**
24
24
  * It is called only on `dao.create` method.
25
25
  * Dao.create method is called in:
@@ -93,7 +93,7 @@ export declare enum CommonDaoLogLevel {
93
93
  */
94
94
  DATA_FULL = 30
95
95
  }
96
- export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
96
+ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID = BM['id']> {
97
97
  db: CommonDB;
98
98
  table: string;
99
99
  /**
@@ -138,7 +138,7 @@ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity
138
138
  * @default false
139
139
  */
140
140
  logStarted?: boolean;
141
- hooks?: Partial<CommonDaoHooks<BM, DBM>>;
141
+ hooks?: Partial<CommonDaoHooks<BM, DBM, ID>>;
142
142
  /**
143
143
  * Defaults to true.
144
144
  * Set to false to disable auto-generation of `id`.
@@ -87,12 +87,12 @@ export declare class DBQuery<ROW extends ObjectWithId> {
87
87
  /**
88
88
  * DBQuery that has additional method to support Fluent API style.
89
89
  */
90
- export declare class RunnableDBQuery<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> extends DBQuery<DBM> {
91
- dao: CommonDao<BM, DBM>;
90
+ export declare class RunnableDBQuery<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID = BM['id']> extends DBQuery<DBM> {
91
+ dao: CommonDao<BM, DBM, ID>;
92
92
  /**
93
93
  * Pass `table` to override table.
94
94
  */
95
- constructor(dao: CommonDao<BM, DBM>, table?: string);
95
+ constructor(dao: CommonDao<BM, DBM, ID>, table?: string);
96
96
  runQuery(opt?: CommonDaoOptions): Promise<BM[]>;
97
97
  runQuerySingleColumn<T = any>(opt?: CommonDaoOptions): Promise<T[]>;
98
98
  runQueryAsDBM(opt?: CommonDaoOptions): Promise<DBM[]>;
@@ -104,8 +104,8 @@ export declare class RunnableDBQuery<BM extends BaseDBEntity, DBM extends BaseDB
104
104
  streamQueryAsDBMForEach(mapper: AsyncMapper<DBM, void>, opt?: CommonDaoStreamForEachOptions<DBM>): Promise<void>;
105
105
  streamQuery(opt?: CommonDaoStreamOptions<BM>): ReadableTyped<BM>;
106
106
  streamQueryAsDBM(opt?: CommonDaoStreamOptions<DBM>): ReadableTyped<DBM>;
107
- queryIds(opt?: CommonDaoOptions): Promise<string[]>;
108
- streamQueryIds(opt?: CommonDaoStreamOptions<string>): ReadableTyped<string>;
109
- streamQueryIdsForEach(mapper: AsyncMapper<string, void>, opt?: CommonDaoStreamForEachOptions<string>): Promise<void>;
107
+ queryIds(opt?: CommonDaoOptions): Promise<ID[]>;
108
+ streamQueryIds(opt?: CommonDaoStreamOptions<ID>): ReadableTyped<ID>;
109
+ streamQueryIdsForEach(mapper: AsyncMapper<ID, void>, opt?: CommonDaoStreamForEachOptions<ID>): Promise<void>;
110
110
  deleteByQuery(opt?: CommonDaoStreamDeleteOptions<DBM>): Promise<number>;
111
111
  }
@@ -24,5 +24,5 @@ export declare class FakeDBTransaction implements DBTransaction {
24
24
  rollback(): Promise<void>;
25
25
  getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: CommonDBOptions): Promise<ROW[]>;
26
26
  saveBatch<ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
27
- deleteByIds(table: string, ids: string[], opt?: CommonDBOptions | undefined): Promise<number>;
27
+ deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number>;
28
28
  }
package/package.json CHANGED
@@ -13,7 +13,6 @@
13
13
  "@naturalcycles/nodejs-lib": "^13.1.1"
14
14
  },
15
15
  "devDependencies": {
16
- "@biomejs/biome": "^1.8.3",
17
16
  "@naturalcycles/bench-lib": "^3.0.0",
18
17
  "@naturalcycles/dev-lib": "^15.4.1",
19
18
  "@types/node": "^22.1.0",
@@ -46,7 +45,7 @@
46
45
  "engines": {
47
46
  "node": ">=20.13"
48
47
  },
49
- "version": "9.16.0",
48
+ "version": "9.18.0",
50
49
  "description": "Lowest Common Denominator API to supported Databases",
51
50
  "keywords": [
52
51
  "db",
@@ -17,26 +17,26 @@ import {
17
17
  import { CommonDB } from '../common.db'
18
18
  import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model'
19
19
 
20
- export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntity> {
20
+ export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']> {
21
21
  /**
22
22
  * Allows to override the id generation function.
23
23
  * By default it uses `stringId` from nodejs-lib
24
24
  * (which uses lowercase alphanumberic alphabet and the size of 16).
25
25
  */
26
- createRandomId: () => string
26
+ createRandomId: () => ID
27
27
 
28
28
  /**
29
29
  * createNaturalId hook is called (tried) first.
30
30
  * If it doesn't exist - createRandomId is called.
31
31
  */
32
- createNaturalId: (obj: DBM | BM) => string
32
+ createNaturalId: (obj: DBM | BM) => ID
33
33
 
34
34
  /**
35
35
  * It's a counter-part of `createNaturalId`.
36
36
  * Allows to provide a parser function to parse "natural id" into
37
37
  * DBM components (e.g accountId and some other property that is part of the id).
38
38
  */
39
- parseNaturalId: (id: string) => Partial<DBM>
39
+ parseNaturalId: (id: ID) => Partial<DBM>
40
40
 
41
41
  /**
42
42
  * It is called only on `dao.create` method.
@@ -118,7 +118,11 @@ export enum CommonDaoLogLevel {
118
118
  DATA_FULL = 30,
119
119
  }
120
120
 
121
- export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
121
+ export interface CommonDaoCfg<
122
+ BM extends BaseDBEntity,
123
+ DBM extends BaseDBEntity = BM,
124
+ ID = BM['id'],
125
+ > {
122
126
  db: CommonDB
123
127
  table: string
124
128
 
@@ -174,7 +178,7 @@ export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity
174
178
  logStarted?: boolean
175
179
 
176
180
  // Hooks are designed with inspiration from got/ky interface
177
- hooks?: Partial<CommonDaoHooks<BM, DBM>>
181
+ hooks?: Partial<CommonDaoHooks<BM, DBM, ID>>
178
182
 
179
183
  /**
180
184
  * Defaults to true.
@@ -72,8 +72,8 @@ const isCI = !!process.env['CI']
72
72
  * BM = Backend model (optimized for API access)
73
73
  * TM = Transport model (optimized to be sent over the wire)
74
74
  */
75
- export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
76
- constructor(public cfg: CommonDaoCfg<BM, DBM>) {
75
+ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID = BM['id']> {
76
+ constructor(public cfg: CommonDaoCfg<BM, DBM, ID>) {
77
77
  this.cfg = {
78
78
  // Default is to NOT log in AppEngine and in CI,
79
79
  // otherwise to log Operations
@@ -93,11 +93,11 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
93
93
  anonymize: dbm => dbm,
94
94
  onValidationError: err => err,
95
95
  ...cfg.hooks,
96
- } satisfies Partial<CommonDaoHooks<BM, DBM>>,
96
+ } satisfies Partial<CommonDaoHooks<BM, DBM, ID>>,
97
97
  }
98
98
 
99
99
  if (this.cfg.generateId) {
100
- this.cfg.hooks!.createRandomId ||= () => stringId()
100
+ this.cfg.hooks!.createRandomId ||= () => stringId() as ID
101
101
  } else {
102
102
  delete this.cfg.hooks!.createRandomId
103
103
  }
@@ -112,15 +112,16 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
112
112
  }
113
113
 
114
114
  // GET
115
- async getById(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
116
- async getById(id?: string | null, opt?: CommonDaoOptions): Promise<BM | null>
117
- async getById(id?: string | null, opt: CommonDaoOptions = {}): Promise<BM | null> {
115
+ // overrides are disabled now, as they obfuscate errors when ID branded type is used
116
+ // async getById(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
117
+ // async getById(id?: ID | null, opt?: CommonDaoOptions): Promise<BM | null>
118
+ async getById(id?: ID | null, opt: CommonDaoOptions = {}): Promise<BM | null> {
118
119
  if (!id) return null
119
120
  const op = `getById(${id})`
120
121
  const table = opt.table || this.cfg.table
121
122
  const started = this.logStarted(op, table)
122
123
 
123
- let dbm = (await (opt.tx || this.cfg.db).getByIds<DBM>(table, [id]))[0]
124
+ let dbm = (await (opt.tx || this.cfg.db).getByIds<DBM>(table, [id as string]))[0]
124
125
  if (dbm && this.cfg.hooks!.afterLoad) {
125
126
  dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
126
127
  }
@@ -130,21 +131,21 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
130
131
  return bm || null
131
132
  }
132
133
 
133
- async getByIdOrEmpty(id: string, part: Partial<BM> = {}, opt?: CommonDaoOptions): Promise<BM> {
134
+ async getByIdOrEmpty(id: ID, part: Partial<BM> = {}, opt?: CommonDaoOptions): Promise<BM> {
134
135
  const bm = await this.getById(id, opt)
135
136
  if (bm) return bm
136
137
 
137
138
  return this.create({ ...part, id }, opt)
138
139
  }
139
140
 
140
- async getByIdAsDBM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
141
- async getByIdAsDBM(id?: string | null, opt?: CommonDaoOptions): Promise<DBM | null>
142
- async getByIdAsDBM(id?: string | null, opt: CommonDaoOptions = {}): Promise<DBM | null> {
141
+ // async getByIdAsDBM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
142
+ // async getByIdAsDBM(id?: ID | null, opt?: CommonDaoOptions): Promise<DBM | null>
143
+ async getByIdAsDBM(id?: ID | null, opt: CommonDaoOptions = {}): Promise<DBM | null> {
143
144
  if (!id) return null
144
145
  const op = `getByIdAsDBM(${id})`
145
146
  const table = opt.table || this.cfg.table
146
147
  const started = this.logStarted(op, table)
147
- let [dbm] = await (opt.tx || this.cfg.db).getByIds<DBM>(table, [id])
148
+ let [dbm] = await (opt.tx || this.cfg.db).getByIds<DBM>(table, [id as string])
148
149
  if (dbm && this.cfg.hooks!.afterLoad) {
149
150
  dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
150
151
  }
@@ -154,12 +155,12 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
154
155
  return dbm || null
155
156
  }
156
157
 
157
- async getByIds(ids: string[], opt: CommonDaoOptions = {}): Promise<BM[]> {
158
+ async getByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<BM[]> {
158
159
  if (!ids.length) return []
159
160
  const op = `getByIds ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
160
161
  const table = opt.table || this.cfg.table
161
162
  const started = this.logStarted(op, table)
162
- let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids)
163
+ let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids as string[])
163
164
  if (this.cfg.hooks!.afterLoad && dbms.length) {
164
165
  dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
165
166
  _isTruthy,
@@ -171,12 +172,12 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
171
172
  return bms
172
173
  }
173
174
 
174
- async getByIdsAsDBM(ids: string[], opt: CommonDaoOptions = {}): Promise<DBM[]> {
175
+ async getByIdsAsDBM(ids: ID[], opt: CommonDaoOptions = {}): Promise<DBM[]> {
175
176
  if (!ids.length) return []
176
177
  const op = `getByIdsAsDBM ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
177
178
  const table = opt.table || this.cfg.table
178
179
  const started = this.logStarted(op, table)
179
- let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids)
180
+ let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids as string[])
180
181
  if (this.cfg.hooks!.afterLoad && dbms.length) {
181
182
  dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
182
183
  _isTruthy,
@@ -187,7 +188,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
187
188
  return dbms
188
189
  }
189
190
 
190
- async requireById(id: string, opt: CommonDaoOptions = {}): Promise<BM> {
191
+ async requireById(id: ID, opt: CommonDaoOptions = {}): Promise<BM> {
191
192
  const r = await this.getById(id, opt)
192
193
  if (!r) {
193
194
  this.throwRequiredError(id, opt)
@@ -195,7 +196,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
195
196
  return r
196
197
  }
197
198
 
198
- async requireByIdAsDBM(id: string, opt: CommonDaoOptions = {}): Promise<DBM> {
199
+ async requireByIdAsDBM(id: ID, opt: CommonDaoOptions = {}): Promise<DBM> {
199
200
  const r = await this.getByIdAsDBM(id, opt)
200
201
  if (!r) {
201
202
  this.throwRequiredError(id, opt)
@@ -203,7 +204,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
203
204
  return r
204
205
  }
205
206
 
206
- private throwRequiredError(id: string, opt: CommonDaoOptions): never {
207
+ private throwRequiredError(id: ID, opt: CommonDaoOptions): never {
207
208
  const table = opt.table || this.cfg.table
208
209
  throw new AppError(`DB row required, but not found in ${table}`, {
209
210
  table,
@@ -261,8 +262,8 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
261
262
  /**
262
263
  * Pass `table` to override table
263
264
  */
264
- query(table?: string): RunnableDBQuery<BM, DBM> {
265
- return new RunnableDBQuery<BM, DBM>(this, table)
265
+ query(table?: string): RunnableDBQuery<BM, DBM, ID> {
266
+ return new RunnableDBQuery<BM, DBM, ID>(this, table)
266
267
  }
267
268
 
268
269
  async runQuery(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<BM[]> {
@@ -546,23 +547,23 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
546
547
  )
547
548
  }
548
549
 
549
- async queryIds(q: DBQuery<DBM>, opt: CommonDaoOptions = {}): Promise<string[]> {
550
+ async queryIds(q: DBQuery<DBM>, opt: CommonDaoOptions = {}): Promise<ID[]> {
550
551
  this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
551
552
  q.table = opt.table || q.table
552
553
  const { rows } = await this.cfg.db.runQuery(q.select(['id']), opt)
553
- return rows.map(r => r.id)
554
+ return rows.map(r => r.id as ID)
554
555
  }
555
556
 
556
- streamQueryIds(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<string> = {}): ReadableTyped<string> {
557
+ streamQueryIds(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<ID> = {}): ReadableTyped<ID> {
557
558
  this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
558
559
  q.table = opt.table || q.table
559
560
  opt.errorMode ||= ErrorMode.SUPPRESS
560
561
 
561
562
  // Experimental: using `.map()`
562
- const stream: ReadableTyped<string> = this.cfg.db
563
+ const stream: ReadableTyped<ID> = this.cfg.db
563
564
  .streamQuery<DBM>(q.select(['id']), opt)
564
565
  // .on('error', err => stream.emit('error', err))
565
- .map((r: ObjectWithId) => r.id)
566
+ .map((r: ObjectWithId) => r.id as ID)
566
567
 
567
568
  // const stream: ReadableTyped<string> = this.cfg.db
568
569
  // .streamQuery<DBM>(q.select(['id']), opt)
@@ -578,8 +579,8 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
578
579
 
579
580
  async streamQueryIdsForEach(
580
581
  q: DBQuery<DBM>,
581
- mapper: AsyncMapper<string, void>,
582
- opt: CommonDaoStreamForEachOptions<string> = {},
582
+ mapper: AsyncMapper<ID, void>,
583
+ opt: CommonDaoStreamForEachOptions<ID> = {},
583
584
  ): Promise<void> {
584
585
  this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
585
586
  q.table = opt.table || q.table
@@ -594,7 +595,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
594
595
  count++
595
596
  return r.id
596
597
  }),
597
- transformMap<string, void>(mapper, {
598
+ transformMap<ID, void>(mapper, {
598
599
  ...opt,
599
600
  predicate: _passthroughPredicate,
600
601
  }),
@@ -627,7 +628,8 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
627
628
  }
628
629
 
629
630
  if (this.cfg.generateId) {
630
- obj.id ||= this.cfg.hooks!.createNaturalId?.(obj as any) || this.cfg.hooks!.createRandomId!()
631
+ obj.id ||= (this.cfg.hooks!.createNaturalId?.(obj as any) ||
632
+ this.cfg.hooks!.createRandomId!()) as T['id']
631
633
  }
632
634
 
633
635
  return obj as T
@@ -644,7 +646,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
644
646
  * 3. Saves (as fast as possible since the read) with the Patch applied, but only if the data has changed.
645
647
  */
646
648
  async patchById(
647
- id: string,
649
+ id: ID,
648
650
  patch: Partial<BM>,
649
651
  opt: CommonDaoPatchByIdOptions<DBM> = {},
650
652
  ): Promise<BM> {
@@ -681,7 +683,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
681
683
  * Like patchById, but runs all operations within a Transaction.
682
684
  */
683
685
  async patchByIdInTransaction(
684
- id: string,
686
+ id: ID,
685
687
  patch: Partial<BM>,
686
688
  opt?: CommonDaoPatchByIdOptions<DBM>,
687
689
  ): Promise<BM> {
@@ -716,7 +718,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
716
718
  }
717
719
  Object.assign(bm, patch)
718
720
  } else {
719
- const loaded = await this.getById(bm.id, opt)
721
+ const loaded = await this.getById(bm.id as ID, opt)
720
722
 
721
723
  if (loaded) {
722
724
  const loadedWithPatch: BM = {
@@ -999,19 +1001,19 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
999
1001
  /**
1000
1002
  * @returns number of deleted items
1001
1003
  */
1002
- async deleteById(id?: string | null, opt: CommonDaoOptions = {}): Promise<number> {
1004
+ async deleteById(id?: ID | null, opt: CommonDaoOptions = {}): Promise<number> {
1003
1005
  if (!id) return 0
1004
1006
  return await this.deleteByIds([id], opt)
1005
1007
  }
1006
1008
 
1007
- async deleteByIds(ids: string[], opt: CommonDaoOptions = {}): Promise<number> {
1009
+ async deleteByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<number> {
1008
1010
  if (!ids.length) return 0
1009
1011
  this.requireWriteAccess()
1010
1012
  this.requireObjectMutability(opt)
1011
1013
  const op = `deleteByIds(${ids.join(', ')})`
1012
1014
  const table = opt.table || this.cfg.table
1013
1015
  const started = this.logStarted(op, table)
1014
- const count = await (opt.tx || this.cfg.db).deleteByIds(table, ids, opt)
1016
+ const count = await (opt.tx || this.cfg.db).deleteByIds(table, ids as string[], opt)
1015
1017
  this.logSaveResult(started, op, table)
1016
1018
  return count
1017
1019
  }
@@ -1066,15 +1068,11 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1066
1068
  return deleted
1067
1069
  }
1068
1070
 
1069
- async updateById(id: string, patch: DBPatch<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
1071
+ async updateById(id: ID, patch: DBPatch<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
1070
1072
  return await this.updateByQuery(this.query().filterEq('id', id), patch, opt)
1071
1073
  }
1072
1074
 
1073
- async updateByIds(
1074
- ids: string[],
1075
- patch: DBPatch<DBM>,
1076
- opt: CommonDaoOptions = {},
1077
- ): Promise<number> {
1075
+ async updateByIds(ids: ID[], patch: DBPatch<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
1078
1076
  if (!ids.length) return 0
1079
1077
  return await this.updateByQuery(this.query().filterIn('id', ids), patch, opt)
1080
1078
  }
@@ -1104,7 +1102,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1104
1102
 
1105
1103
  // optimization: no need to run full joi DBM validation, cause BM validation will be run
1106
1104
  // const dbm = this.anyToDBM(_dbm, opt)
1107
- let dbm: DBM = { ..._dbm, ...this.cfg.hooks!.parseNaturalId!(_dbm.id) }
1105
+ let dbm: DBM = { ..._dbm, ...this.cfg.hooks!.parseNaturalId!(_dbm.id as ID) }
1108
1106
 
1109
1107
  if (opt.anonymize) {
1110
1108
  dbm = this.cfg.hooks!.anonymize!(dbm)
@@ -1150,7 +1148,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
1150
1148
  // this shouldn't be happening on load! but should on save!
1151
1149
  // this.assignIdCreatedUpdated(dbm, opt)
1152
1150
 
1153
- dbm = { ...dbm, ...this.cfg.hooks!.parseNaturalId!(dbm.id) }
1151
+ dbm = { ...dbm, ...this.cfg.hooks!.parseNaturalId!(dbm.id as ID) }
1154
1152
 
1155
1153
  // todo: is this the right place?
1156
1154
  // todo: is anyToDBM even needed?
@@ -1375,17 +1373,17 @@ export class CommonDaoTransaction {
1375
1373
  }
1376
1374
  }
1377
1375
 
1378
- async getById<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
1379
- dao: CommonDao<BM, DBM>,
1380
- id?: string | null,
1376
+ async getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
1377
+ dao: CommonDao<BM, DBM, ID>,
1378
+ id?: ID | null,
1381
1379
  opt?: CommonDaoOptions,
1382
1380
  ): Promise<BM | null> {
1383
1381
  return await dao.getById(id, { ...opt, tx: this.tx })
1384
1382
  }
1385
1383
 
1386
- async getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
1387
- dao: CommonDao<BM, DBM>,
1388
- ids: string[],
1384
+ async getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
1385
+ dao: CommonDao<BM, DBM, ID>,
1386
+ ids: ID[],
1389
1387
  opt?: CommonDaoOptions,
1390
1388
  ): Promise<BM[]> {
1391
1389
  return await dao.getByIds(ids, { ...opt, tx: this.tx })
@@ -1428,8 +1426,8 @@ export class CommonDaoTransaction {
1428
1426
  *
1429
1427
  * So, this method is a rather simple convenience "Object.assign and then save".
1430
1428
  */
1431
- async patch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
1432
- dao: CommonDao<BM, DBM>,
1429
+ async patch<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
1430
+ dao: CommonDao<BM, DBM, ID>,
1433
1431
  bm: BM,
1434
1432
  patch: Partial<BM>,
1435
1433
  opt?: CommonDaoSaveOptions<BM, DBM>,
@@ -1439,16 +1437,20 @@ export class CommonDaoTransaction {
1439
1437
  return await dao.save(bm, { ...opt, skipIfEquals, tx: this.tx })
1440
1438
  }
1441
1439
 
1442
- async deleteById(
1443
- dao: CommonDao<any>,
1444
- id?: string | null,
1440
+ async deleteById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
1441
+ dao: CommonDao<BM, DBM, ID>,
1442
+ id?: ID | null,
1445
1443
  opt?: CommonDaoOptions,
1446
1444
  ): Promise<number> {
1447
1445
  if (!id) return 0
1448
1446
  return await this.deleteByIds(dao, [id], opt)
1449
1447
  }
1450
1448
 
1451
- async deleteByIds(dao: CommonDao<any>, ids: string[], opt?: CommonDaoOptions): Promise<number> {
1449
+ async deleteByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
1450
+ dao: CommonDao<BM, DBM, ID>,
1451
+ ids: ID[],
1452
+ opt?: CommonDaoOptions,
1453
+ ): Promise<number> {
1452
1454
  return await dao.deleteByIds(ids, { ...opt, tx: this.tx })
1453
1455
  }
1454
1456
  }
@@ -193,7 +193,7 @@ export class DBQuery<ROW extends ObjectWithId> {
193
193
  }
194
194
 
195
195
  prettyConditions(): string[] {
196
- const tokens = []
196
+ const tokens: string[] = []
197
197
 
198
198
  // if (this.name) {
199
199
  // tokens.push(`"${this.name}"`)
@@ -240,12 +240,13 @@ export class DBQuery<ROW extends ObjectWithId> {
240
240
  export class RunnableDBQuery<
241
241
  BM extends BaseDBEntity,
242
242
  DBM extends BaseDBEntity = BM,
243
+ ID = BM['id'],
243
244
  > extends DBQuery<DBM> {
244
245
  /**
245
246
  * Pass `table` to override table.
246
247
  */
247
248
  constructor(
248
- public dao: CommonDao<BM, DBM>,
249
+ public dao: CommonDao<BM, DBM, ID>,
249
250
  table?: string,
250
251
  ) {
251
252
  super(table || dao.cfg.table)
@@ -301,17 +302,17 @@ export class RunnableDBQuery<
301
302
  return this.dao.streamQueryAsDBM(this, opt)
302
303
  }
303
304
 
304
- async queryIds(opt?: CommonDaoOptions): Promise<string[]> {
305
+ async queryIds(opt?: CommonDaoOptions): Promise<ID[]> {
305
306
  return await this.dao.queryIds(this, opt)
306
307
  }
307
308
 
308
- streamQueryIds(opt?: CommonDaoStreamOptions<string>): ReadableTyped<string> {
309
+ streamQueryIds(opt?: CommonDaoStreamOptions<ID>): ReadableTyped<ID> {
309
310
  return this.dao.streamQueryIds(this, opt)
310
311
  }
311
312
 
312
313
  async streamQueryIdsForEach(
313
- mapper: AsyncMapper<string, void>,
314
- opt?: CommonDaoStreamForEachOptions<string>,
314
+ mapper: AsyncMapper<ID, void>,
315
+ opt?: CommonDaoStreamForEachOptions<ID>,
315
316
  ): Promise<void> {
316
317
  await this.dao.streamQueryIdsForEach(this, mapper, opt)
317
318
  }
@@ -120,11 +120,7 @@ export class FakeDBTransaction implements DBTransaction {
120
120
  ): Promise<void> {
121
121
  await this.db.saveBatch(table, rows, opt)
122
122
  }
123
- async deleteByIds(
124
- table: string,
125
- ids: string[],
126
- opt?: CommonDBOptions | undefined,
127
- ): Promise<number> {
123
+ async deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number> {
128
124
  return await this.db.deleteByIds(table, ids, opt)
129
125
  }
130
126
  }