@naturalcycles/db-lib 8.50.2 → 8.51.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/dist/adapter/file/file.db.d.ts +8 -8
- package/dist/adapter/file/file.db.model.d.ts +4 -4
- package/dist/adapter/file/inMemory.persistence.plugin.d.ts +1 -1
- package/dist/adapter/file/localFile.persistence.plugin.d.ts +1 -1
- package/dist/adapter/file/noop.persistence.plugin.d.ts +1 -1
- package/dist/adapter/inmemory/inMemory.db.d.ts +8 -8
- package/dist/adapter/inmemory/queryInMemory.d.ts +1 -1
- package/dist/common.db.d.ts +12 -12
- package/dist/commondao/common.dao.d.ts +17 -6
- package/dist/commondao/common.dao.js +75 -1
- package/dist/commondao/common.dao.model.d.ts +20 -23
- package/dist/db.model.d.ts +2 -1
- package/dist/db.model.js +1 -0
- package/dist/kv/commonKeyValueDB.d.ts +9 -9
- package/dist/kv/commonKeyValueDao.d.ts +3 -3
- package/dist/query/dbQuery.d.ts +10 -8
- package/dist/query/dbQuery.js +6 -0
- package/dist/testing/daoTest.js +1 -0
- package/package.json +1 -1
- package/src/adapter/file/file.db.model.ts +4 -4
- package/src/adapter/file/file.db.ts +12 -12
- package/src/adapter/file/inMemory.persistence.plugin.ts +1 -1
- package/src/adapter/file/localFile.persistence.plugin.ts +1 -1
- package/src/adapter/file/noop.persistence.plugin.ts +1 -1
- package/src/adapter/inmemory/inMemory.db.ts +11 -11
- package/src/adapter/inmemory/queryInMemory.ts +1 -4
- package/src/common.db.ts +24 -18
- package/src/commondao/common.dao.model.ts +32 -27
- package/src/commondao/common.dao.ts +108 -10
- package/src/db.model.ts +1 -0
- package/src/kv/commonKeyValueDB.ts +9 -9
- package/src/kv/commonKeyValueDao.ts +3 -3
- package/src/query/dbQuery.ts +18 -8
- package/src/testing/daoTest.ts +2 -0
|
@@ -3,10 +3,10 @@ import { DBSaveBatchOperation } from '../../db.model'
|
|
|
3
3
|
import type { DBQueryOrder } from '../../query/dbQuery'
|
|
4
4
|
|
|
5
5
|
export interface FileDBPersistencePlugin {
|
|
6
|
-
ping()
|
|
7
|
-
getTables()
|
|
8
|
-
loadFile<ROW extends
|
|
9
|
-
saveFiles(ops: DBSaveBatchOperation<any>[])
|
|
6
|
+
ping: () => Promise<void>
|
|
7
|
+
getTables: () => Promise<string[]>
|
|
8
|
+
loadFile: <ROW extends ObjectWithId>(table: string) => Promise<ROW[]>
|
|
9
|
+
saveFiles: (ops: DBSaveBatchOperation<any>[]) => Promise<void>
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface FileDBCfg {
|
|
@@ -65,13 +65,13 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
65
65
|
return tables
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
override async getByIds<ROW extends
|
|
68
|
+
override async getByIds<ROW extends ObjectWithId>(
|
|
69
69
|
table: string,
|
|
70
70
|
ids: ROW['id'][],
|
|
71
71
|
_opt?: CommonDBOptions,
|
|
72
72
|
): Promise<ROW[]> {
|
|
73
73
|
const byId = _by(await this.loadFile<ROW>(table), r => r.id)
|
|
74
|
-
return ids.map(id => byId[id
|
|
74
|
+
return ids.map(id => byId[id]!).filter(Boolean)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
override async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
@@ -115,7 +115,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
115
115
|
await pMap(
|
|
116
116
|
tables,
|
|
117
117
|
async table => {
|
|
118
|
-
const rows
|
|
118
|
+
const rows = await this.loadFile(table)
|
|
119
119
|
data[table] = _by(rows, r => r.id)
|
|
120
120
|
},
|
|
121
121
|
{ concurrency: 16 },
|
|
@@ -168,7 +168,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
override async runQuery<ROW extends
|
|
171
|
+
override async runQuery<ROW extends ObjectWithId>(
|
|
172
172
|
q: DBQuery<ROW>,
|
|
173
173
|
_opt?: CommonDBOptions,
|
|
174
174
|
): Promise<RunQueryResult<ROW>> {
|
|
@@ -177,14 +177,14 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
override async runQueryCount<ROW extends
|
|
180
|
+
override async runQueryCount<ROW extends ObjectWithId>(
|
|
181
181
|
q: DBQuery<ROW>,
|
|
182
182
|
_opt?: CommonDBOptions,
|
|
183
183
|
): Promise<number> {
|
|
184
184
|
return (await this.loadFile(q.table)).length
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
override streamQuery<ROW extends
|
|
187
|
+
override streamQuery<ROW extends ObjectWithId>(
|
|
188
188
|
q: DBQuery<ROW>,
|
|
189
189
|
opt?: CommonDBStreamOptions,
|
|
190
190
|
): ReadableTyped<ROW> {
|
|
@@ -198,7 +198,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
198
198
|
return readable
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
override async deleteByQuery<ROW extends
|
|
201
|
+
override async deleteByQuery<ROW extends ObjectWithId>(
|
|
202
202
|
q: DBQuery<ROW>,
|
|
203
203
|
_opt?: CommonDBOptions,
|
|
204
204
|
): Promise<number> {
|
|
@@ -206,7 +206,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
206
206
|
|
|
207
207
|
let deleted = 0
|
|
208
208
|
queryInMemory(q, _stringMapValues(byId)).forEach(r => {
|
|
209
|
-
delete byId[r.id
|
|
209
|
+
delete byId[r.id]
|
|
210
210
|
deleted++
|
|
211
211
|
})
|
|
212
212
|
|
|
@@ -217,7 +217,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
217
217
|
return deleted
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
override async getTableSchema<ROW extends
|
|
220
|
+
override async getTableSchema<ROW extends ObjectWithId>(
|
|
221
221
|
table: string,
|
|
222
222
|
): Promise<JsonSchemaRootObject<ROW>> {
|
|
223
223
|
const rows = await this.loadFile(table)
|
|
@@ -228,7 +228,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
// wrapper, to handle logging
|
|
231
|
-
async loadFile<ROW extends
|
|
231
|
+
async loadFile<ROW extends ObjectWithId>(table: string): Promise<ROW[]> {
|
|
232
232
|
const started = this.logStarted(`loadFile(${table})`)
|
|
233
233
|
const rows = await this.cfg.plugin.loadFile<ROW>(table)
|
|
234
234
|
this.logFinished(started, `loadFile(${table}) ${rows.length} row(s)`)
|
|
@@ -236,7 +236,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
// wrapper, to handle logging, sorting rows before saving
|
|
239
|
-
async saveFile<ROW extends
|
|
239
|
+
async saveFile<ROW extends ObjectWithId>(table: string, _rows: ROW[]): Promise<void> {
|
|
240
240
|
// if (!_rows.length) return // NO, it should be able to save file with 0 rows!
|
|
241
241
|
|
|
242
242
|
// Sort the rows, if needed
|
|
@@ -257,7 +257,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
257
257
|
this.logFinished(started, op)
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
private sortRows<ROW extends
|
|
260
|
+
private sortRows<ROW extends ObjectWithId>(rows: ROW[]): ROW[] {
|
|
261
261
|
rows = rows.map(r => _filterUndefinedValues(r))
|
|
262
262
|
|
|
263
263
|
if (this.cfg.sortOnSave) {
|
|
@@ -14,7 +14,7 @@ export class InMemoryPersistencePlugin implements FileDBPersistencePlugin {
|
|
|
14
14
|
return Object.keys(this.data)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
async loadFile<ROW extends
|
|
17
|
+
async loadFile<ROW extends ObjectWithId>(table: string): Promise<ROW[]> {
|
|
18
18
|
return Object.values(this.data[table] || ({} as any))
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -46,7 +46,7 @@ export class LocalFilePersistencePlugin implements FileDBPersistencePlugin {
|
|
|
46
46
|
.map(f => f.split('.ndjson')[0]!)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
async loadFile<ROW extends
|
|
49
|
+
async loadFile<ROW extends ObjectWithId>(table: string): Promise<ROW[]> {
|
|
50
50
|
await fs.ensureDir(this.cfg.storagePath)
|
|
51
51
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`
|
|
52
52
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`
|
|
@@ -9,7 +9,7 @@ export class NoopPersistencePlugin implements FileDBPersistencePlugin {
|
|
|
9
9
|
return []
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
async loadFile<ROW extends
|
|
12
|
+
async loadFile<ROW extends ObjectWithId>(_table: string): Promise<ROW[]> {
|
|
13
13
|
return []
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -120,7 +120,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
120
120
|
return Object.keys(this.data).filter(t => t.startsWith(this.cfg.tablesPrefix))
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
async getTableSchema<ROW extends
|
|
123
|
+
async getTableSchema<ROW extends ObjectWithId>(
|
|
124
124
|
_table: string,
|
|
125
125
|
): Promise<JsonSchemaRootObject<ROW>> {
|
|
126
126
|
const table = this.cfg.tablesPrefix + _table
|
|
@@ -130,7 +130,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
async createTable<ROW extends
|
|
133
|
+
async createTable<ROW extends ObjectWithId>(
|
|
134
134
|
_table: string,
|
|
135
135
|
_schema: JsonSchemaObject<ROW>,
|
|
136
136
|
opt: CommonDBCreateOptions = {},
|
|
@@ -143,14 +143,14 @@ export class InMemoryDB implements CommonDB {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
async getByIds<ROW extends
|
|
146
|
+
async getByIds<ROW extends ObjectWithId>(
|
|
147
147
|
_table: string,
|
|
148
148
|
ids: ROW['id'][],
|
|
149
149
|
_opt?: CommonDBOptions,
|
|
150
150
|
): Promise<ROW[]> {
|
|
151
151
|
const table = this.cfg.tablesPrefix + _table
|
|
152
152
|
this.data[table] ||= {}
|
|
153
|
-
return ids.map(id => this.data[table]![id
|
|
153
|
+
return ids.map(id => this.data[table]![id] as ROW).filter(Boolean)
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
@@ -184,7 +184,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
184
184
|
})
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
async deleteByQuery<ROW extends
|
|
187
|
+
async deleteByQuery<ROW extends ObjectWithId>(
|
|
188
188
|
q: DBQuery<ROW>,
|
|
189
189
|
_opt?: CommonDBOptions,
|
|
190
190
|
): Promise<number> {
|
|
@@ -192,14 +192,14 @@ export class InMemoryDB implements CommonDB {
|
|
|
192
192
|
this.data[table] ||= {}
|
|
193
193
|
let count = 0
|
|
194
194
|
queryInMemory(q, Object.values(this.data[table] || {}) as ROW[]).forEach(r => {
|
|
195
|
-
if (!this.data[table]![r.id
|
|
196
|
-
delete this.data[table]![r.id
|
|
195
|
+
if (!this.data[table]![r.id]) return
|
|
196
|
+
delete this.data[table]![r.id]
|
|
197
197
|
count++
|
|
198
198
|
})
|
|
199
199
|
return count
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
async updateByQuery<ROW extends
|
|
202
|
+
async updateByQuery<ROW extends ObjectWithId>(
|
|
203
203
|
q: DBQuery<ROW>,
|
|
204
204
|
patch: DBPatch<ROW>,
|
|
205
205
|
): Promise<number> {
|
|
@@ -221,7 +221,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
221
221
|
return rows.length
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
async runQuery<ROW extends
|
|
224
|
+
async runQuery<ROW extends ObjectWithId>(
|
|
225
225
|
q: DBQuery<ROW>,
|
|
226
226
|
_opt?: CommonDBOptions,
|
|
227
227
|
): Promise<RunQueryResult<ROW>> {
|
|
@@ -229,7 +229,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
229
229
|
return { rows: queryInMemory(q, Object.values(this.data[table] || {}) as ROW[]) }
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
async runQueryCount<ROW extends
|
|
232
|
+
async runQueryCount<ROW extends ObjectWithId>(
|
|
233
233
|
q: DBQuery<ROW>,
|
|
234
234
|
_opt?: CommonDBOptions,
|
|
235
235
|
): Promise<number> {
|
|
@@ -237,7 +237,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
237
237
|
return queryInMemory<any>(q, Object.values(this.data[table] || {})).length
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
streamQuery<ROW extends
|
|
240
|
+
streamQuery<ROW extends ObjectWithId>(
|
|
241
241
|
q: DBQuery<ROW>,
|
|
242
242
|
_opt?: CommonDBOptions,
|
|
243
243
|
): ReadableTyped<ROW> {
|
|
@@ -18,10 +18,7 @@ const FILTER_FNS: Record<DBQueryFilterOperator, FilterFn> = {
|
|
|
18
18
|
|
|
19
19
|
// Important: q.table is not used in this function, so tablesPrefix is not needed.
|
|
20
20
|
// But should be careful here..
|
|
21
|
-
export function queryInMemory<ROW extends
|
|
22
|
-
q: DBQuery<ROW>,
|
|
23
|
-
rows: ROW[] = [],
|
|
24
|
-
): ROW[] {
|
|
21
|
+
export function queryInMemory<ROW extends ObjectWithId>(q: DBQuery<ROW>, rows: ROW[] = []): ROW[] {
|
|
25
22
|
// .filter
|
|
26
23
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
27
24
|
rows = q._filters.reduce((rows, filter) => {
|
package/src/common.db.ts
CHANGED
|
@@ -18,12 +18,12 @@ export interface CommonDB {
|
|
|
18
18
|
* It SHOULD fail if DB setup is wrong (e.g on wrong credentials).
|
|
19
19
|
* It SHOULD succeed if e.g getByIds(['nonExistingKey']) doesn't throw.
|
|
20
20
|
*/
|
|
21
|
-
ping()
|
|
21
|
+
ping: () => Promise<void>
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Return all tables (table names) available in this DB.
|
|
25
25
|
*/
|
|
26
|
-
getTables()
|
|
26
|
+
getTables: () => Promise<string[]>
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* $id of the schema SHOULD be like this:
|
|
@@ -31,61 +31,67 @@ export interface CommonDB {
|
|
|
31
31
|
*
|
|
32
32
|
* This is important for the code to rely on it, and it's verified by dbTest
|
|
33
33
|
*/
|
|
34
|
-
getTableSchema<ROW extends ObjectWithId>(table: string)
|
|
34
|
+
getTableSchema: <ROW extends ObjectWithId>(table: string) => Promise<JsonSchemaRootObject<ROW>>
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Will do like `create table ...` for mysql.
|
|
38
38
|
* Caution! dropIfExists defaults to false. If set to true - will actually DROP the table!
|
|
39
39
|
*/
|
|
40
|
-
createTable<ROW extends ObjectWithId>(
|
|
40
|
+
createTable: <ROW extends ObjectWithId>(
|
|
41
41
|
table: string,
|
|
42
42
|
schema: JsonSchemaObject<ROW>,
|
|
43
43
|
opt?: CommonDBCreateOptions,
|
|
44
|
-
)
|
|
44
|
+
) => Promise<void>
|
|
45
45
|
|
|
46
46
|
// GET
|
|
47
47
|
/**
|
|
48
48
|
* Order of items returned is not guaranteed to match order of ids.
|
|
49
49
|
* (Such limitation exists because Datastore doesn't support it).
|
|
50
50
|
*/
|
|
51
|
-
getByIds<ROW extends ObjectWithId>(
|
|
51
|
+
getByIds: <ROW extends ObjectWithId>(
|
|
52
52
|
table: string,
|
|
53
53
|
ids: ROW['id'][],
|
|
54
54
|
opt?: CommonDBOptions,
|
|
55
|
-
)
|
|
55
|
+
) => Promise<ROW[]>
|
|
56
56
|
|
|
57
57
|
// QUERY
|
|
58
58
|
/**
|
|
59
59
|
* Order by 'id' is not supported by all implementations (for example, Datastore doesn't support it).
|
|
60
60
|
*/
|
|
61
|
-
runQuery<ROW extends ObjectWithId>(
|
|
61
|
+
runQuery: <ROW extends ObjectWithId>(
|
|
62
62
|
q: DBQuery<ROW>,
|
|
63
63
|
opt?: CommonDBOptions,
|
|
64
|
-
)
|
|
64
|
+
) => Promise<RunQueryResult<ROW>>
|
|
65
65
|
|
|
66
|
-
runQueryCount<ROW extends ObjectWithId>(
|
|
66
|
+
runQueryCount: <ROW extends ObjectWithId>(
|
|
67
|
+
q: DBQuery<ROW>,
|
|
68
|
+
opt?: CommonDBOptions,
|
|
69
|
+
) => Promise<number>
|
|
67
70
|
|
|
68
|
-
streamQuery<ROW extends ObjectWithId>(
|
|
71
|
+
streamQuery: <ROW extends ObjectWithId>(
|
|
69
72
|
q: DBQuery<ROW>,
|
|
70
73
|
opt?: CommonDBStreamOptions,
|
|
71
|
-
)
|
|
74
|
+
) => ReadableTyped<ROW>
|
|
72
75
|
|
|
73
76
|
// SAVE
|
|
74
77
|
/**
|
|
75
78
|
* rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment).
|
|
76
79
|
*/
|
|
77
|
-
saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
80
|
+
saveBatch: <ROW extends Partial<ObjectWithId>>(
|
|
78
81
|
table: string,
|
|
79
82
|
rows: ROW[],
|
|
80
83
|
opt?: CommonDBSaveOptions<ROW>,
|
|
81
|
-
)
|
|
84
|
+
) => Promise<void>
|
|
82
85
|
|
|
83
86
|
// DELETE
|
|
84
87
|
/**
|
|
85
88
|
* Returns number of deleted items.
|
|
86
89
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
87
90
|
*/
|
|
88
|
-
deleteByQuery<ROW extends ObjectWithId>(
|
|
91
|
+
deleteByQuery: <ROW extends ObjectWithId>(
|
|
92
|
+
q: DBQuery<ROW>,
|
|
93
|
+
opt?: CommonDBOptions,
|
|
94
|
+
) => Promise<number>
|
|
89
95
|
|
|
90
96
|
/**
|
|
91
97
|
* Applies patch to the rows returned by the query.
|
|
@@ -105,16 +111,16 @@ export interface CommonDB {
|
|
|
105
111
|
*
|
|
106
112
|
* Returns number of rows affected.
|
|
107
113
|
*/
|
|
108
|
-
updateByQuery<ROW extends ObjectWithId>(
|
|
114
|
+
updateByQuery: <ROW extends ObjectWithId>(
|
|
109
115
|
q: DBQuery<ROW>,
|
|
110
116
|
patch: DBPatch<ROW>,
|
|
111
117
|
opt?: CommonDBOptions,
|
|
112
|
-
)
|
|
118
|
+
) => Promise<number>
|
|
113
119
|
|
|
114
120
|
// TRANSACTION
|
|
115
121
|
/**
|
|
116
122
|
* Should be implemented as a Transaction (best effort), which means that
|
|
117
123
|
* either ALL or NONE of the operations should be applied.
|
|
118
124
|
*/
|
|
119
|
-
commitTransaction(tx: DBTransaction, opt?: CommonDBOptions)
|
|
125
|
+
commitTransaction: (tx: DBTransaction, opt?: CommonDBOptions) => Promise<void>
|
|
120
126
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CommonLogger,
|
|
3
|
+
ErrorMode,
|
|
4
|
+
ObjectWithId,
|
|
5
|
+
Saved,
|
|
6
|
+
ZodError,
|
|
7
|
+
ZodSchema,
|
|
8
|
+
} from '@naturalcycles/js-lib'
|
|
2
9
|
import {
|
|
3
10
|
AjvSchema,
|
|
4
11
|
AjvValidationError,
|
|
@@ -12,25 +19,24 @@ import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../
|
|
|
12
19
|
|
|
13
20
|
export interface CommonDaoHooks<
|
|
14
21
|
BM extends Partial<ObjectWithId<ID>>,
|
|
15
|
-
DBM extends
|
|
16
|
-
|
|
22
|
+
DBM extends ObjectWithId<ID>,
|
|
23
|
+
TM,
|
|
24
|
+
ID extends string | number,
|
|
17
25
|
> {
|
|
18
|
-
createRandomId()
|
|
26
|
+
createRandomId: () => ID
|
|
19
27
|
/**
|
|
20
28
|
* createNaturalId hook is called (tried) first.
|
|
21
29
|
* If it doesn't exist - createRandomId is called.
|
|
22
30
|
*/
|
|
23
|
-
createNaturalId(obj: DBM | BM)
|
|
24
|
-
parseNaturalId(id: ID)
|
|
25
|
-
beforeCreate(bm: Partial<BM>)
|
|
26
|
-
beforeDBMValidate(dbm: Partial<DBM>)
|
|
27
|
-
beforeDBMToBM(dbm: DBM)
|
|
28
|
-
beforeBMToDBM(bm: BM)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*/
|
|
33
|
-
anonymize(dbm: DBM): DBM
|
|
31
|
+
createNaturalId: (obj: DBM | BM) => ID
|
|
32
|
+
parseNaturalId: (id: ID) => Partial<DBM>
|
|
33
|
+
beforeCreate: (bm: Partial<BM>) => Partial<BM>
|
|
34
|
+
beforeDBMValidate: (dbm: Partial<DBM>) => Partial<DBM>
|
|
35
|
+
beforeDBMToBM: (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>
|
|
36
|
+
beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>
|
|
37
|
+
beforeTMToBM: (tm: TM) => Partial<BM>
|
|
38
|
+
beforeBMToTM: (bm: BM) => Partial<TM>
|
|
39
|
+
anonymize: (dbm: DBM) => DBM
|
|
34
40
|
|
|
35
41
|
/**
|
|
36
42
|
* If hook is defined - allows to prevent or modify the error thrown.
|
|
@@ -38,7 +44,7 @@ export interface CommonDaoHooks<
|
|
|
38
44
|
* Return original `err` to pass the error through (will be thrown in CommonDao).
|
|
39
45
|
* Return modified/new `Error` if needed.
|
|
40
46
|
*/
|
|
41
|
-
onValidationError(err: JoiValidationError | AjvValidationError)
|
|
47
|
+
onValidationError: (err: JoiValidationError | AjvValidationError | ZodError) => Error | false
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
export enum CommonDaoLogLevel {
|
|
@@ -61,18 +67,20 @@ export enum CommonDaoLogLevel {
|
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export interface CommonDaoCfg<
|
|
64
|
-
BM extends ObjectWithId<ID
|
|
65
|
-
DBM extends ObjectWithId<ID> = BM
|
|
66
|
-
|
|
70
|
+
BM extends Partial<ObjectWithId<ID>>,
|
|
71
|
+
DBM extends ObjectWithId<ID> = Saved<BM>,
|
|
72
|
+
TM = BM,
|
|
73
|
+
ID extends string | number = string,
|
|
67
74
|
> {
|
|
68
75
|
db: CommonDB
|
|
69
76
|
table: string
|
|
70
77
|
|
|
71
78
|
/**
|
|
72
|
-
* Joi or
|
|
79
|
+
* Joi, AjvSchema or ZodSchema is supported.
|
|
73
80
|
*/
|
|
74
|
-
dbmSchema?: ObjectSchemaTyped<DBM> | AjvSchema<DBM>
|
|
75
|
-
bmSchema?: ObjectSchemaTyped<BM> | AjvSchema<BM>
|
|
81
|
+
dbmSchema?: ObjectSchemaTyped<DBM> | AjvSchema<DBM> | ZodSchema<DBM>
|
|
82
|
+
bmSchema?: ObjectSchemaTyped<BM> | AjvSchema<BM> | ZodSchema<BM>
|
|
83
|
+
tmSchema?: ObjectSchemaTyped<TM> | AjvSchema<TM> | ZodSchema<TM>
|
|
76
84
|
|
|
77
85
|
excludeFromIndexes?: (keyof DBM)[]
|
|
78
86
|
|
|
@@ -84,8 +92,6 @@ export interface CommonDaoCfg<
|
|
|
84
92
|
* `delete*` and `patch` will throw.
|
|
85
93
|
*
|
|
86
94
|
* You can still override saveMethod, or set opt.allowMutability to allow deletion.
|
|
87
|
-
*
|
|
88
|
-
* todo: consider merging it with readOnly, as it's almost the same
|
|
89
95
|
*/
|
|
90
96
|
immutable?: boolean
|
|
91
97
|
|
|
@@ -111,7 +117,7 @@ export interface CommonDaoCfg<
|
|
|
111
117
|
logStarted?: boolean
|
|
112
118
|
|
|
113
119
|
// Hooks are designed with inspiration from got/ky interface
|
|
114
|
-
hooks?: Partial<CommonDaoHooks<BM, DBM, ID>>
|
|
120
|
+
hooks?: Partial<CommonDaoHooks<BM, DBM, TM, ID>>
|
|
115
121
|
|
|
116
122
|
/**
|
|
117
123
|
* Defaults to 'string'
|
|
@@ -128,7 +134,6 @@ export interface CommonDaoCfg<
|
|
|
128
134
|
/**
|
|
129
135
|
* See the same option in CommonDB.
|
|
130
136
|
* Defaults to false normally.
|
|
131
|
-
* "Generated" means "generated by the underlying DB" (e.g MySQL autoincrement).
|
|
132
137
|
*/
|
|
133
138
|
assignGeneratedIds?: boolean
|
|
134
139
|
|
|
@@ -226,7 +231,7 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
226
231
|
/**
|
|
227
232
|
* All properties default to undefined.
|
|
228
233
|
*/
|
|
229
|
-
export interface CommonDaoSaveOptions<DBM extends
|
|
234
|
+
export interface CommonDaoSaveOptions<DBM extends ObjectWithId>
|
|
230
235
|
extends CommonDaoOptions,
|
|
231
236
|
CommonDBSaveOptions<DBM> {
|
|
232
237
|
/**
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
_since,
|
|
7
7
|
_truncate,
|
|
8
8
|
_uniqBy,
|
|
9
|
+
AnyObject,
|
|
9
10
|
AppError,
|
|
10
11
|
AsyncMapper,
|
|
11
12
|
ErrorMode,
|
|
@@ -15,6 +16,9 @@ import {
|
|
|
15
16
|
pMap,
|
|
16
17
|
Saved,
|
|
17
18
|
Unsaved,
|
|
19
|
+
ZodSchema,
|
|
20
|
+
ZodValidationError,
|
|
21
|
+
zSafeValidate,
|
|
18
22
|
} from '@naturalcycles/js-lib'
|
|
19
23
|
import {
|
|
20
24
|
_pipeline,
|
|
@@ -62,13 +66,15 @@ const isCI = !!process.env['CI']
|
|
|
62
66
|
*
|
|
63
67
|
* DBM = Database model (how it's stored in DB)
|
|
64
68
|
* BM = Backend model (optimized for API access)
|
|
69
|
+
* TM = Transport model (optimized to be sent over the wire)
|
|
65
70
|
*/
|
|
66
71
|
export class CommonDao<
|
|
67
|
-
BM extends ObjectWithId<ID
|
|
68
|
-
DBM extends ObjectWithId<ID> = BM
|
|
69
|
-
|
|
72
|
+
BM extends Partial<ObjectWithId<ID>>,
|
|
73
|
+
DBM extends ObjectWithId<ID> = Saved<BM>,
|
|
74
|
+
TM extends AnyObject = BM,
|
|
75
|
+
ID extends string | number = NonNullable<BM['id']>,
|
|
70
76
|
> {
|
|
71
|
-
constructor(public cfg: CommonDaoCfg<BM, DBM, ID>) {
|
|
77
|
+
constructor(public cfg: CommonDaoCfg<BM, DBM, TM, ID>) {
|
|
72
78
|
this.cfg = {
|
|
73
79
|
// Default is to NOT log in AppEngine and in CI,
|
|
74
80
|
// otherwise to log Operations
|
|
@@ -87,6 +93,8 @@ export class CommonDao<
|
|
|
87
93
|
beforeDBMValidate: dbm => dbm,
|
|
88
94
|
beforeDBMToBM: dbm => dbm as any,
|
|
89
95
|
beforeBMToDBM: bm => bm as any,
|
|
96
|
+
beforeTMToBM: tm => tm as any,
|
|
97
|
+
beforeBMToTM: bm => bm as any,
|
|
90
98
|
anonymize: dbm => dbm,
|
|
91
99
|
onValidationError: err => err,
|
|
92
100
|
...cfg.hooks,
|
|
@@ -159,6 +167,24 @@ export class CommonDao<
|
|
|
159
167
|
return dbm || null
|
|
160
168
|
}
|
|
161
169
|
|
|
170
|
+
async getByIdAsTM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
|
|
171
|
+
async getByIdAsTM(id?: ID | null, opt?: CommonDaoOptions): Promise<TM | null>
|
|
172
|
+
async getByIdAsTM(id?: ID | null, opt: CommonDaoOptions = {}): Promise<TM | null> {
|
|
173
|
+
if (!id) return null
|
|
174
|
+
const op = `getByIdAsTM(${id})`
|
|
175
|
+
const table = opt.table || this.cfg.table
|
|
176
|
+
const started = this.logStarted(op, table)
|
|
177
|
+
const [dbm] = await this.cfg.db.getByIds<DBM>(table, [id])
|
|
178
|
+
if (opt.raw) {
|
|
179
|
+
this.logResult(started, op, dbm, table)
|
|
180
|
+
return (dbm as any) || null
|
|
181
|
+
}
|
|
182
|
+
const bm = await this.dbmToBM(dbm, opt)
|
|
183
|
+
const tm = this.bmToTM(bm, opt)
|
|
184
|
+
this.logResult(started, op, tm, table)
|
|
185
|
+
return tm || null
|
|
186
|
+
}
|
|
187
|
+
|
|
162
188
|
async getByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<Saved<BM>[]> {
|
|
163
189
|
const op = `getByIds ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
|
|
164
190
|
const table = opt.table || this.cfg.table
|
|
@@ -256,8 +282,8 @@ export class CommonDao<
|
|
|
256
282
|
/**
|
|
257
283
|
* Pass `table` to override table
|
|
258
284
|
*/
|
|
259
|
-
query(table?: string): RunnableDBQuery<BM, DBM, ID> {
|
|
260
|
-
return new RunnableDBQuery<BM, DBM, ID>(this, table)
|
|
285
|
+
query(table?: string): RunnableDBQuery<BM, DBM, TM, ID> {
|
|
286
|
+
return new RunnableDBQuery<BM, DBM, TM, ID>(this, table)
|
|
261
287
|
}
|
|
262
288
|
|
|
263
289
|
async runQuery(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<Saved<BM>[]> {
|
|
@@ -325,6 +351,29 @@ export class CommonDao<
|
|
|
325
351
|
return { rows: dbms, ...queryResult }
|
|
326
352
|
}
|
|
327
353
|
|
|
354
|
+
async runQueryAsTM(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<TM[]> {
|
|
355
|
+
const { rows } = await this.runQueryExtendedAsTM(q, opt)
|
|
356
|
+
return rows
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async runQueryExtendedAsTM(
|
|
360
|
+
q: DBQuery<DBM>,
|
|
361
|
+
opt: CommonDaoOptions = {},
|
|
362
|
+
): Promise<RunQueryResult<TM>> {
|
|
363
|
+
q.table = opt.table || q.table
|
|
364
|
+
const op = `runQueryAsTM(${q.pretty()})`
|
|
365
|
+
const started = this.logStarted(op, q.table)
|
|
366
|
+
const { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
|
|
367
|
+
const partialQuery = !!q._selectedFieldNames
|
|
368
|
+
const tms =
|
|
369
|
+
partialQuery || opt.raw ? (rows as any[]) : this.bmsToTM(await this.dbmsToBM(rows, opt), opt)
|
|
370
|
+
this.logResult(started, op, tms, q.table)
|
|
371
|
+
return {
|
|
372
|
+
rows: tms,
|
|
373
|
+
...queryResult,
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
328
377
|
async runQueryCount(q: DBQuery<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
|
|
329
378
|
q.table = opt.table || q.table
|
|
330
379
|
const op = `runQueryCount(${q.pretty()})`
|
|
@@ -979,6 +1028,50 @@ export class CommonDao<
|
|
|
979
1028
|
return entities.map(entity => this.anyToDBM(entity, opt))
|
|
980
1029
|
}
|
|
981
1030
|
|
|
1031
|
+
bmToTM(bm: undefined, opt?: CommonDaoOptions): TM | undefined
|
|
1032
|
+
bmToTM(bm?: Saved<BM>, opt?: CommonDaoOptions): TM
|
|
1033
|
+
bmToTM(bm?: Saved<BM>, opt?: CommonDaoOptions): TM | undefined {
|
|
1034
|
+
if (bm === undefined) return
|
|
1035
|
+
|
|
1036
|
+
// optimization: 1 validation is enough
|
|
1037
|
+
// Validate/convert BM
|
|
1038
|
+
// bm gets assigned to the new reference
|
|
1039
|
+
// bm = this.validateAndConvert(bm, this.cfg.bmSchema, DBModelType.BM, opt)
|
|
1040
|
+
|
|
1041
|
+
// BM > TM
|
|
1042
|
+
const tm = this.cfg.hooks!.beforeBMToTM!(bm as any)
|
|
1043
|
+
|
|
1044
|
+
// Validate/convert DBM
|
|
1045
|
+
return this.validateAndConvert(tm, this.cfg.tmSchema, DBModelType.TM, opt)
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
bmsToTM(bms: Saved<BM>[], opt: CommonDaoOptions = {}): TM[] {
|
|
1049
|
+
// try/catch?
|
|
1050
|
+
return bms.map(bm => this.bmToTM(bm, opt))
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
tmToBM(tm: undefined, opt?: CommonDaoOptions): undefined
|
|
1054
|
+
tmToBM(tm?: TM, opt?: CommonDaoOptions): BM
|
|
1055
|
+
tmToBM(tm?: TM, opt: CommonDaoOptions = {}): BM | undefined {
|
|
1056
|
+
if (!tm) return
|
|
1057
|
+
|
|
1058
|
+
// optimization: 1 validation is enough
|
|
1059
|
+
// Validate/convert TM
|
|
1060
|
+
// bm gets assigned to the new reference
|
|
1061
|
+
// tm = this.validateAndConvert(tm, this.cfg.tmSchema, DBModelType.TM, opt)
|
|
1062
|
+
|
|
1063
|
+
// TM > BM
|
|
1064
|
+
const bm = this.cfg.hooks!.beforeTMToBM!(tm) as BM
|
|
1065
|
+
|
|
1066
|
+
// Validate/convert BM
|
|
1067
|
+
return this.validateAndConvert<BM>(bm, this.cfg.bmSchema, DBModelType.BM, opt)
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
tmsToBM(tms: TM[], opt: CommonDaoOptions = {}): BM[] {
|
|
1071
|
+
// try/catch?
|
|
1072
|
+
return tms.map(tm => this.tmToBM(tm, opt))
|
|
1073
|
+
}
|
|
1074
|
+
|
|
982
1075
|
/**
|
|
983
1076
|
* Returns *converted value*.
|
|
984
1077
|
* Validates (unless `skipValidation=true` passed).
|
|
@@ -987,8 +1080,8 @@ export class CommonDao<
|
|
|
987
1080
|
*/
|
|
988
1081
|
validateAndConvert<IN, OUT = IN>(
|
|
989
1082
|
obj: Partial<IN>,
|
|
990
|
-
schema: ObjectSchemaTyped<IN> | AjvSchema<IN> | undefined,
|
|
991
|
-
modelType
|
|
1083
|
+
schema: ObjectSchemaTyped<IN> | AjvSchema<IN> | ZodSchema<IN> | undefined,
|
|
1084
|
+
modelType: DBModelType,
|
|
992
1085
|
opt: CommonDaoOptions = {},
|
|
993
1086
|
): OUT {
|
|
994
1087
|
// `raw` option completely bypasses any processing
|
|
@@ -1023,10 +1116,15 @@ export class CommonDao<
|
|
|
1023
1116
|
const table = opt.table || this.cfg.table
|
|
1024
1117
|
const objectName = table + (modelType || '')
|
|
1025
1118
|
|
|
1026
|
-
let error: JoiValidationError | AjvValidationError | undefined
|
|
1119
|
+
let error: JoiValidationError | AjvValidationError | ZodValidationError<IN> | undefined
|
|
1027
1120
|
let convertedValue: any
|
|
1028
1121
|
|
|
1029
|
-
if (schema instanceof
|
|
1122
|
+
if (schema instanceof ZodSchema) {
|
|
1123
|
+
// Zod schema
|
|
1124
|
+
const vr = zSafeValidate(obj as IN, schema)
|
|
1125
|
+
error = vr.error
|
|
1126
|
+
convertedValue = vr.data
|
|
1127
|
+
} else if (schema instanceof AjvSchema) {
|
|
1030
1128
|
// Ajv schema
|
|
1031
1129
|
convertedValue = obj // because Ajv mutates original object
|
|
1032
1130
|
|