@naturalcycles/db-lib 10.17.2 → 10.19.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/commondao/common.dao.d.ts +43 -7
- package/dist/commondao/common.dao.js +118 -0
- package/dist/commondao/common.dao.model.d.ts +1 -6
- package/dist/commondb/base.common.db.d.ts +5 -1
- package/dist/commondb/base.common.db.js +12 -0
- package/dist/commondb/common.db.d.ts +68 -9
- package/dist/commondb/common.db.js +2 -0
- package/dist/inmemory/inMemory.db.d.ts +4 -0
- package/dist/inmemory/inMemory.db.js +29 -2
- package/dist/query/dbQuery.d.ts +1 -1
- package/dist/testing/test.model.d.ts +1 -0
- package/dist/testing/test.model.js +1 -0
- package/package.json +1 -1
- package/src/commondao/common.dao.model.ts +1 -12
- package/src/commondao/common.dao.ts +199 -17
- package/src/commondb/base.common.db.ts +28 -0
- package/src/commondb/common.db.ts +89 -10
- package/src/inmemory/inMemory.db.ts +51 -2
- package/src/query/dbQuery.ts +1 -1
- package/src/testing/test.model.ts +1 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Transform } from 'node:stream';
|
|
2
2
|
import type { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib/json-schema';
|
|
3
3
|
import type { CommonLogger } from '@naturalcycles/js-lib/log';
|
|
4
|
-
import type
|
|
4
|
+
import { type AsyncIndexedMapper, type BaseDBEntity, type NonNegativeInteger, type StringMap, type UnixTimestampMillis, type Unsaved } from '@naturalcycles/js-lib/types';
|
|
5
5
|
import { type ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
|
|
6
6
|
import type { CommonDBTransactionOptions, DBTransaction, RunQueryResult } from '../db.model.js';
|
|
7
7
|
import type { DBQuery } from '../query/dbQuery.js';
|
|
@@ -14,7 +14,7 @@ import type { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoP
|
|
|
14
14
|
* BM = Backend model (optimized for API access)
|
|
15
15
|
* TM = Transport model (optimized to be sent over the wire)
|
|
16
16
|
*/
|
|
17
|
-
export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID = BM['id']> {
|
|
17
|
+
export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID extends string = BM['id']> {
|
|
18
18
|
cfg: CommonDaoCfg<BM, DBM, ID>;
|
|
19
19
|
constructor(cfg: CommonDaoCfg<BM, DBM, ID>);
|
|
20
20
|
create(part?: Partial<BM>, opt?: CommonDaoOptions): BM;
|
|
@@ -167,6 +167,26 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
|
|
|
167
167
|
* Proxy to this.cfg.db.ping
|
|
168
168
|
*/
|
|
169
169
|
ping(): Promise<void>;
|
|
170
|
+
withId(id: ID): DaoWithId<typeof this>;
|
|
171
|
+
withIds(ids: ID[]): DaoWithIds<typeof this>;
|
|
172
|
+
toSave(input: BM | BM[]): DaoWithRows<typeof this>;
|
|
173
|
+
/**
|
|
174
|
+
* Load rows (by their ids) from Multiple tables at once.
|
|
175
|
+
* An optimized way to load data, minimizing DB round-trips.
|
|
176
|
+
*
|
|
177
|
+
* @experimental.
|
|
178
|
+
*/
|
|
179
|
+
static multiGet<MAP extends Record<string, DaoWithIds<AnyDao> | DaoWithId<AnyDao>>>(inputMap: MAP, opt?: CommonDaoReadOptions): Promise<{
|
|
180
|
+
[K in keyof MAP]: MAP[K] extends DaoWithIds<any> ? InferBM<MAP[K]['dao']>[] : InferBM<MAP[K]['dao']> | null;
|
|
181
|
+
}>;
|
|
182
|
+
private static prepareMultiGetIds;
|
|
183
|
+
private static multiGetMapByTableById;
|
|
184
|
+
private static prepareMultiGetOutput;
|
|
185
|
+
/**
|
|
186
|
+
* Very @experimental.
|
|
187
|
+
*/
|
|
188
|
+
static multiDeleteByIds(inputs: DaoWithIds<any>[], _opt?: CommonDaoOptions): Promise<NonNegativeInteger>;
|
|
189
|
+
static multiSave(inputs: DaoWithRows<any>[], opt?: CommonDaoSaveBatchOptions<any>): Promise<void>;
|
|
170
190
|
createTransaction(opt?: CommonDBTransactionOptions): Promise<CommonDaoTransaction>;
|
|
171
191
|
runInTransaction<T = void>(fn: CommonDaoTransactionFn<T>, opt?: CommonDBTransactionOptions): Promise<T>;
|
|
172
192
|
/**
|
|
@@ -202,8 +222,8 @@ export declare class CommonDaoTransaction {
|
|
|
202
222
|
* Never throws.
|
|
203
223
|
*/
|
|
204
224
|
rollback(): Promise<void>;
|
|
205
|
-
getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, id?: ID | null, opt?: CommonDaoReadOptions): Promise<BM | null>;
|
|
206
|
-
getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, ids: ID[], opt?: CommonDaoReadOptions): Promise<BM[]>;
|
|
225
|
+
getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(dao: CommonDao<BM, DBM, ID>, id?: ID | null, opt?: CommonDaoReadOptions): Promise<BM | null>;
|
|
226
|
+
getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(dao: CommonDao<BM, DBM, ID>, ids: ID[], opt?: CommonDaoReadOptions): Promise<BM[]>;
|
|
207
227
|
save<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<BM>;
|
|
208
228
|
saveBatch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, bms: Unsaved<BM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM[]>;
|
|
209
229
|
/**
|
|
@@ -213,7 +233,23 @@ export declare class CommonDaoTransaction {
|
|
|
213
233
|
*
|
|
214
234
|
* So, this method is a rather simple convenience "Object.assign and then save".
|
|
215
235
|
*/
|
|
216
|
-
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>;
|
|
217
|
-
deleteById<
|
|
218
|
-
deleteByIds<
|
|
236
|
+
patch<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(dao: CommonDao<BM, DBM, ID>, bm: BM, patch: Partial<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<BM>;
|
|
237
|
+
deleteById<DAO extends AnyDao>(dao: DAO, id?: InferID<DAO> | null, opt?: CommonDaoOptions): Promise<number>;
|
|
238
|
+
deleteByIds<DAO extends AnyDao>(dao: DAO, ids: InferID<DAO>[], opt?: CommonDaoOptions): Promise<number>;
|
|
239
|
+
}
|
|
240
|
+
export interface DaoWithIds<DAO extends AnyDao> {
|
|
241
|
+
dao: DAO;
|
|
242
|
+
ids: string[];
|
|
243
|
+
}
|
|
244
|
+
export interface DaoWithId<DAO extends AnyDao> {
|
|
245
|
+
dao: DAO;
|
|
246
|
+
id: string;
|
|
247
|
+
}
|
|
248
|
+
export interface DaoWithRows<DAO extends AnyDao> {
|
|
249
|
+
dao: DAO;
|
|
250
|
+
rows: InferBM<DAO>[];
|
|
219
251
|
}
|
|
252
|
+
type InferBM<DAO> = DAO extends CommonDao<infer BM> ? BM : never;
|
|
253
|
+
type InferID<DAO> = DAO extends CommonDao<any, any, infer ID> ? ID : never;
|
|
254
|
+
export type AnyDao = CommonDao<any>;
|
|
255
|
+
export {};
|
|
@@ -7,6 +7,7 @@ import { _deepJsonEquals } from '@naturalcycles/js-lib/object/deepEquals.js';
|
|
|
7
7
|
import { _deepCopy, _filterUndefinedValues, _objectAssignExact, } from '@naturalcycles/js-lib/object/object.util.js';
|
|
8
8
|
import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
|
|
9
9
|
import { _truncate } from '@naturalcycles/js-lib/string/string.util.js';
|
|
10
|
+
import { _stringMapEntries, _stringMapValues, } from '@naturalcycles/js-lib/types';
|
|
10
11
|
import { _passthroughPredicate, _typeCast, SKIP } from '@naturalcycles/js-lib/types';
|
|
11
12
|
import { stringId } from '@naturalcycles/nodejs-lib';
|
|
12
13
|
import { transformFlatten } from '@naturalcycles/nodejs-lib/stream';
|
|
@@ -981,6 +982,122 @@ export class CommonDao {
|
|
|
981
982
|
async ping() {
|
|
982
983
|
await this.cfg.db.ping();
|
|
983
984
|
}
|
|
985
|
+
withId(id) {
|
|
986
|
+
return {
|
|
987
|
+
dao: this,
|
|
988
|
+
id,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
withIds(ids) {
|
|
992
|
+
return {
|
|
993
|
+
dao: this,
|
|
994
|
+
ids,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
toSave(input) {
|
|
998
|
+
return {
|
|
999
|
+
dao: this,
|
|
1000
|
+
rows: [input].flat(),
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Load rows (by their ids) from Multiple tables at once.
|
|
1005
|
+
* An optimized way to load data, minimizing DB round-trips.
|
|
1006
|
+
*
|
|
1007
|
+
* @experimental.
|
|
1008
|
+
*/
|
|
1009
|
+
static async multiGet(inputMap, opt = {}) {
|
|
1010
|
+
const db = Object.values(inputMap)[0]?.dao.cfg.db;
|
|
1011
|
+
if (!db) {
|
|
1012
|
+
return {};
|
|
1013
|
+
}
|
|
1014
|
+
const idsByTable = CommonDao.prepareMultiGetIds(inputMap);
|
|
1015
|
+
// todo: support tx
|
|
1016
|
+
const dbmsByTable = await db.multiGet(idsByTable, opt);
|
|
1017
|
+
const dbmByTableById = CommonDao.multiGetMapByTableById(dbmsByTable);
|
|
1018
|
+
return (await CommonDao.prepareMultiGetOutput(inputMap, dbmByTableById, opt));
|
|
1019
|
+
}
|
|
1020
|
+
static prepareMultiGetIds(inputMap) {
|
|
1021
|
+
const idSetByTable = {};
|
|
1022
|
+
for (const input of _stringMapValues(inputMap)) {
|
|
1023
|
+
const { table } = input.dao.cfg;
|
|
1024
|
+
idSetByTable[table] ||= new Set();
|
|
1025
|
+
if ('id' in input) {
|
|
1026
|
+
// Singular
|
|
1027
|
+
idSetByTable[table].add(input.id);
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
// Plural
|
|
1031
|
+
for (const id of input.ids) {
|
|
1032
|
+
idSetByTable[table].add(id);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
const idsByTable = {};
|
|
1037
|
+
for (const [table, idSet] of _stringMapEntries(idSetByTable)) {
|
|
1038
|
+
idsByTable[table] = [...idSet];
|
|
1039
|
+
}
|
|
1040
|
+
return idsByTable;
|
|
1041
|
+
}
|
|
1042
|
+
static multiGetMapByTableById(dbmsByTable) {
|
|
1043
|
+
// We create this "map of maps", to be able to track the results back to the input props
|
|
1044
|
+
// This is needed to support:
|
|
1045
|
+
// - having multiple props from the same table
|
|
1046
|
+
const dbmByTableById = {};
|
|
1047
|
+
for (const [table, dbms] of _stringMapEntries(dbmsByTable)) {
|
|
1048
|
+
dbmByTableById[table] ||= {};
|
|
1049
|
+
for (const dbm of dbms) {
|
|
1050
|
+
dbmByTableById[table][dbm.id] = dbm;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return dbmByTableById;
|
|
1054
|
+
}
|
|
1055
|
+
static async prepareMultiGetOutput(inputMap, dbmByTableById, opt = {}) {
|
|
1056
|
+
const bmsByProp = {};
|
|
1057
|
+
// Loop over input props again, to produce the output of the same shape as requested
|
|
1058
|
+
await pMap(_stringMapEntries(inputMap), async ([prop, input]) => {
|
|
1059
|
+
const { dao } = input;
|
|
1060
|
+
const { table } = dao.cfg;
|
|
1061
|
+
if ('id' in input) {
|
|
1062
|
+
// Singular
|
|
1063
|
+
const dbm = dbmByTableById[table][input.id];
|
|
1064
|
+
bmsByProp[prop] = (await dao.dbmToBM(dbm, opt)) || null;
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
// Plural
|
|
1068
|
+
// We apply filtering, to be able to support multiple input props fetching from the same table.
|
|
1069
|
+
// Without filtering - every prop will get ALL rows from that table.
|
|
1070
|
+
const dbms = input.ids.map(id => dbmByTableById[table][id]).filter(_isTruthy);
|
|
1071
|
+
bmsByProp[prop] = await dao.dbmsToBM(dbms, opt);
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
return bmsByProp;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Very @experimental.
|
|
1078
|
+
*/
|
|
1079
|
+
static async multiDeleteByIds(inputs, _opt = {}) {
|
|
1080
|
+
if (!inputs.length)
|
|
1081
|
+
return 0;
|
|
1082
|
+
const { db } = inputs[0].dao.cfg;
|
|
1083
|
+
const idsByTable = {};
|
|
1084
|
+
for (const { dao, ids } of inputs) {
|
|
1085
|
+
idsByTable[dao.cfg.table] = ids;
|
|
1086
|
+
}
|
|
1087
|
+
return await db.multiDelete(idsByTable);
|
|
1088
|
+
}
|
|
1089
|
+
static async multiSave(inputs, opt = {}) {
|
|
1090
|
+
if (!inputs.length)
|
|
1091
|
+
return;
|
|
1092
|
+
const { db } = inputs[0].dao.cfg;
|
|
1093
|
+
const dbmsByTable = {};
|
|
1094
|
+
await pMap(inputs, async ({ dao, rows }) => {
|
|
1095
|
+
const { table } = dao.cfg;
|
|
1096
|
+
rows.forEach(bm => dao.assignIdCreatedUpdated(bm, opt));
|
|
1097
|
+
dbmsByTable[table] = await dao.bmsToDBM(rows, opt);
|
|
1098
|
+
});
|
|
1099
|
+
await db.multiSave(dbmsByTable);
|
|
1100
|
+
}
|
|
984
1101
|
async createTransaction(opt) {
|
|
985
1102
|
const tx = await this.cfg.db.createTransaction(opt);
|
|
986
1103
|
return new CommonDaoTransaction(tx, this.cfg.logger);
|
|
@@ -1134,6 +1251,7 @@ export class CommonDaoTransaction {
|
|
|
1134
1251
|
Object.assign(bm, patch);
|
|
1135
1252
|
return await dao.save(bm, { ...opt, skipIfEquals, tx: this.tx });
|
|
1136
1253
|
}
|
|
1254
|
+
// todo: use AnyDao/Infer in other methods as well, if this works well
|
|
1137
1255
|
async deleteById(dao, id, opt) {
|
|
1138
1256
|
if (!id)
|
|
1139
1257
|
return 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ValidationFunction } from '@naturalcycles/js-lib';
|
|
2
2
|
import type { AppError, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
3
3
|
import type { CommonLogger } from '@naturalcycles/js-lib/log';
|
|
4
|
-
import type { BaseDBEntity,
|
|
4
|
+
import type { BaseDBEntity, Promisable, UnixTimestamp } from '@naturalcycles/js-lib/types';
|
|
5
5
|
import type { TransformLogProgressOptions, TransformMapOptions } from '@naturalcycles/nodejs-lib/stream';
|
|
6
6
|
import type { CommonDB } from '../commondb/common.db.js';
|
|
7
7
|
import type { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model.js';
|
|
@@ -332,8 +332,3 @@ export interface CommonDaoStreamOptions<IN> extends CommonDaoReadOptions, Transf
|
|
|
332
332
|
chunkConcurrency?: number;
|
|
333
333
|
}
|
|
334
334
|
export type CommonDaoCreateOptions = CommonDBCreateOptions;
|
|
335
|
-
export interface OnValidationTimeData {
|
|
336
|
-
tookMillis: NumberOfMilliseconds;
|
|
337
|
-
table: string;
|
|
338
|
-
obj: any;
|
|
339
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib/json-schema';
|
|
2
2
|
import type { ObjectWithId, StringMap } from '@naturalcycles/js-lib/types';
|
|
3
3
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
|
|
4
|
-
import type { CommonDBOptions, CommonDBSaveOptions, CommonDBTransactionOptions, DBTransaction, DBTransactionFn, RunQueryResult } from '../db.model.js';
|
|
4
|
+
import type { CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions, CommonDBTransactionOptions, DBTransaction, DBTransactionFn, RunQueryResult } from '../db.model.js';
|
|
5
5
|
import type { DBQuery } from '../query/dbQuery.js';
|
|
6
6
|
import type { CommonDB, CommonDBSupport } from './common.db.js';
|
|
7
7
|
import { CommonDBType } from './common.db.js';
|
|
@@ -19,6 +19,7 @@ export declare class BaseCommonDB implements CommonDB {
|
|
|
19
19
|
getByIds<ROW extends ObjectWithId>(_table: string, _ids: string[]): Promise<ROW[]>;
|
|
20
20
|
deleteByQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<number>;
|
|
21
21
|
patchByQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>, _patch: Partial<ROW>, _opt?: CommonDBOptions): Promise<number>;
|
|
22
|
+
patchById<ROW extends ObjectWithId>(_table: string, _id: string, _patch: Partial<ROW>, _opt?: CommonDBOptions): Promise<void>;
|
|
22
23
|
runQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<RunQueryResult<ROW>>;
|
|
23
24
|
runQueryCount<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<number>;
|
|
24
25
|
saveBatch<ROW extends ObjectWithId>(_table: string, _rows: ROW[], _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
@@ -27,4 +28,7 @@ export declare class BaseCommonDB implements CommonDB {
|
|
|
27
28
|
runInTransaction(fn: DBTransactionFn, _opt?: CommonDBTransactionOptions): Promise<void>;
|
|
28
29
|
createTransaction(_opt?: CommonDBTransactionOptions): Promise<DBTransaction>;
|
|
29
30
|
incrementBatch(_table: string, _prop: string, _incrementMap: StringMap<number>, _opt?: CommonDBOptions): Promise<StringMap<number>>;
|
|
31
|
+
multiGet<ROW extends ObjectWithId>(_map: StringMap<string[]>, _opt?: CommonDBReadOptions): Promise<StringMap<ROW[]>>;
|
|
32
|
+
multiSave<ROW extends ObjectWithId>(_map: StringMap<ROW[]>, _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
33
|
+
multiDelete(_map: StringMap<string[]>, _opt?: CommonDBOptions): Promise<number>;
|
|
30
34
|
}
|
|
@@ -28,6 +28,9 @@ export class BaseCommonDB {
|
|
|
28
28
|
async patchByQuery(_q, _patch, _opt) {
|
|
29
29
|
throw new Error('patchByQuery is not implemented');
|
|
30
30
|
}
|
|
31
|
+
async patchById(_table, _id, _patch, _opt) {
|
|
32
|
+
throw new Error('patchById is not implemented');
|
|
33
|
+
}
|
|
31
34
|
async runQuery(_q) {
|
|
32
35
|
throw new Error('runQuery is not implemented');
|
|
33
36
|
}
|
|
@@ -54,4 +57,13 @@ export class BaseCommonDB {
|
|
|
54
57
|
async incrementBatch(_table, _prop, _incrementMap, _opt) {
|
|
55
58
|
throw new Error('incrementBatch is not implemented');
|
|
56
59
|
}
|
|
60
|
+
async multiGet(_map, _opt) {
|
|
61
|
+
throw new Error('multiGetByIds is not implemented');
|
|
62
|
+
}
|
|
63
|
+
async multiSave(_map, _opt) {
|
|
64
|
+
throw new Error('multiSaveBatch is not implemented');
|
|
65
|
+
}
|
|
66
|
+
async multiDelete(_map, _opt) {
|
|
67
|
+
throw new Error('multiDeleteByIds is not implemented');
|
|
68
|
+
}
|
|
57
69
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib/json-schema';
|
|
2
|
-
import type { ObjectWithId, StringMap } from '@naturalcycles/js-lib/types';
|
|
2
|
+
import type { NonNegativeInteger, ObjectWithId, StringMap } from '@naturalcycles/js-lib/types';
|
|
3
3
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
|
|
4
4
|
import type { CommonDBCreateOptions, CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBTransactionOptions, DBTransaction, DBTransactionFn, RunQueryResult } from '../db.model.js';
|
|
5
5
|
import type { DBQuery } from '../query/dbQuery.js';
|
|
6
|
-
export declare enum CommonDBType {
|
|
7
|
-
'document' = "document",
|
|
8
|
-
'relational' = "relational"
|
|
9
|
-
}
|
|
10
6
|
export interface CommonDB {
|
|
11
7
|
/**
|
|
12
8
|
* Relational databases are expected to return `null` for all missing properties.
|
|
@@ -44,26 +40,83 @@ export interface CommonDB {
|
|
|
44
40
|
* (Such limitation exists because Datastore doesn't support it).
|
|
45
41
|
*/
|
|
46
42
|
getByIds: <ROW extends ObjectWithId>(table: string, ids: string[], opt?: CommonDBReadOptions) => Promise<ROW[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Get rows from multiple tables at once.
|
|
45
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
46
|
+
*
|
|
47
|
+
* Takes `map`, which is a map from "table name" to an array of ids.
|
|
48
|
+
* Example:
|
|
49
|
+
* {
|
|
50
|
+
* 'TableOne': ['id1', 'id2'],
|
|
51
|
+
* 'TableTwo': ['id3'],
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* Returns a map with the same keys (table names) and arrays of rows as values.
|
|
55
|
+
* Even if some table is not found, it will return an empty array of results for that table.
|
|
56
|
+
*
|
|
57
|
+
* @experimental
|
|
58
|
+
*/
|
|
59
|
+
multiGet: <ROW extends ObjectWithId>(idsByTable: StringMap<string[]>, opt?: CommonDBReadOptions) => Promise<StringMap<ROW[]>>;
|
|
47
60
|
/**
|
|
48
61
|
* Order by 'id' is not supported by all implementations (for example, Datastore doesn't support it).
|
|
49
62
|
*/
|
|
50
63
|
runQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBReadOptions) => Promise<RunQueryResult<ROW>>;
|
|
51
|
-
runQueryCount: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBReadOptions) => Promise<
|
|
64
|
+
runQueryCount: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBReadOptions) => Promise<NonNegativeInteger>;
|
|
52
65
|
streamQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBStreamOptions) => ReadableTyped<ROW>;
|
|
53
66
|
/**
|
|
54
67
|
* rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment).
|
|
55
68
|
*/
|
|
56
69
|
saveBatch: <ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>) => Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Save rows for multiple tables at once.
|
|
72
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
73
|
+
*
|
|
74
|
+
* Takes `map`, which is a map from "table name" to an array of rows.
|
|
75
|
+
* Example:
|
|
76
|
+
* {
|
|
77
|
+
* 'TableOne': [{ id: 'id1', ... }, { id: 'id2', ... }],
|
|
78
|
+
* 'TableTwo': [{ id: 'id3', ... }],
|
|
79
|
+
* }
|
|
80
|
+
*
|
|
81
|
+
* @experimental
|
|
82
|
+
*/
|
|
83
|
+
multiSave: <ROW extends ObjectWithId>(rowsByTable: StringMap<ROW[]>, opt?: CommonDBSaveOptions<ROW>) => Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Perform a partial update of a row by its id.
|
|
86
|
+
* Unlike save - doesn't require to first load the doc.
|
|
87
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
88
|
+
*
|
|
89
|
+
* The object with given id has to exist, otherwise an error will be thrown.
|
|
90
|
+
*
|
|
91
|
+
* @experimental
|
|
92
|
+
*/
|
|
93
|
+
patchById: <ROW extends ObjectWithId>(table: string, id: string, patch: Partial<ROW>, opt?: CommonDBOptions) => Promise<void>;
|
|
57
94
|
/**
|
|
58
95
|
* Returns number of deleted items.
|
|
59
96
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
60
97
|
*/
|
|
61
|
-
deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<
|
|
98
|
+
deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<NonNegativeInteger>;
|
|
62
99
|
/**
|
|
100
|
+
* Deletes rows from multiple tables at once.
|
|
101
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
102
|
+
* Takes `map`, which is a map from "table name" to an array of ids to delete.
|
|
103
|
+
* Example:
|
|
104
|
+
* {
|
|
105
|
+
* 'TableOne': ['id1', 'id2'],
|
|
106
|
+
* 'TableTwo': ['id3'],
|
|
107
|
+
* }
|
|
108
|
+
*
|
|
63
109
|
* Returns number of deleted items.
|
|
64
110
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
111
|
+
*
|
|
112
|
+
* @experimental
|
|
65
113
|
*/
|
|
66
|
-
|
|
114
|
+
multiDelete: (idsByTable: StringMap<string[]>, opt?: CommonDBOptions) => Promise<NonNegativeInteger>;
|
|
115
|
+
/**
|
|
116
|
+
* Returns number of deleted items.
|
|
117
|
+
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
118
|
+
*/
|
|
119
|
+
deleteByQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions) => Promise<NonNegativeInteger>;
|
|
67
120
|
/**
|
|
68
121
|
* Applies patch to all the rows that are matched by the query.
|
|
69
122
|
*
|
|
@@ -111,6 +164,10 @@ export interface CommonDB {
|
|
|
111
164
|
*/
|
|
112
165
|
incrementBatch: (table: string, prop: string, incrementMap: StringMap<number>, opt?: CommonDBOptions) => Promise<StringMap<number>>;
|
|
113
166
|
}
|
|
167
|
+
export declare enum CommonDBType {
|
|
168
|
+
'document' = "document",
|
|
169
|
+
'relational' = "relational"
|
|
170
|
+
}
|
|
114
171
|
/**
|
|
115
172
|
* Manifest of supported features.
|
|
116
173
|
*/
|
|
@@ -123,6 +180,7 @@ export interface CommonDBSupport {
|
|
|
123
180
|
insertSaveMethod?: boolean;
|
|
124
181
|
updateSaveMethod?: boolean;
|
|
125
182
|
patchByQuery?: boolean;
|
|
183
|
+
patchById?: boolean;
|
|
126
184
|
increment?: boolean;
|
|
127
185
|
createTable?: boolean;
|
|
128
186
|
tableSchemas?: boolean;
|
|
@@ -131,5 +189,6 @@ export interface CommonDBSupport {
|
|
|
131
189
|
nullValues?: boolean;
|
|
132
190
|
transactions?: boolean;
|
|
133
191
|
timeMachine?: boolean;
|
|
192
|
+
multiTableOperations?: boolean;
|
|
134
193
|
}
|
|
135
|
-
export declare const commonDBFullSupport: CommonDBSupport
|
|
194
|
+
export declare const commonDBFullSupport: Required<CommonDBSupport>;
|
|
@@ -12,6 +12,7 @@ export const commonDBFullSupport = {
|
|
|
12
12
|
insertSaveMethod: true,
|
|
13
13
|
updateSaveMethod: true,
|
|
14
14
|
patchByQuery: true,
|
|
15
|
+
patchById: true,
|
|
15
16
|
increment: true,
|
|
16
17
|
createTable: true,
|
|
17
18
|
tableSchemas: true,
|
|
@@ -20,4 +21,5 @@ export const commonDBFullSupport = {
|
|
|
20
21
|
nullValues: true,
|
|
21
22
|
transactions: true,
|
|
22
23
|
timeMachine: true,
|
|
24
|
+
multiTableOperations: true,
|
|
23
25
|
};
|
|
@@ -50,9 +50,13 @@ export declare class InMemoryDB implements CommonDB {
|
|
|
50
50
|
getTableSchema<ROW extends ObjectWithId>(_table: string): Promise<JsonSchemaRootObject<ROW>>;
|
|
51
51
|
createTable<ROW extends ObjectWithId>(_table: string, _schema: JsonSchemaObject<ROW>, opt?: CommonDBCreateOptions): Promise<void>;
|
|
52
52
|
getByIds<ROW extends ObjectWithId>(_table: string, ids: string[], _opt?: CommonDBOptions): Promise<ROW[]>;
|
|
53
|
+
multiGet<ROW extends ObjectWithId>(map: StringMap<string[]>, _opt?: CommonDBOptions): Promise<StringMap<ROW[]>>;
|
|
53
54
|
saveBatch<ROW extends ObjectWithId>(_table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
55
|
+
multiSave<ROW extends ObjectWithId>(map: StringMap<ROW[]>, opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
56
|
+
patchById<ROW extends ObjectWithId>(_table: string, id: string, patch: Partial<ROW>, _opt?: CommonDBOptions): Promise<void>;
|
|
54
57
|
deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
|
|
55
58
|
deleteByIds(_table: string, ids: string[], _opt?: CommonDBOptions): Promise<number>;
|
|
59
|
+
multiDelete(map: StringMap<string[]>, _opt?: CommonDBOptions): Promise<number>;
|
|
56
60
|
patchByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, patch: Partial<ROW>): Promise<number>;
|
|
57
61
|
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
|
|
58
62
|
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
|
|
@@ -74,18 +74,28 @@ export class InMemoryDB {
|
|
|
74
74
|
this.data[table] ||= {};
|
|
75
75
|
return ids.map(id => this.data[table][id]).filter(Boolean);
|
|
76
76
|
}
|
|
77
|
+
async multiGet(map, _opt = {}) {
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const [tableName, ids] of _stringMapEntries(map)) {
|
|
80
|
+
const table = this.cfg.tablesPrefix + tableName;
|
|
81
|
+
result[table] = ids.map(id => this.data[table]?.[id]).filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
77
85
|
async saveBatch(_table, rows, opt = {}) {
|
|
78
86
|
const table = this.cfg.tablesPrefix + _table;
|
|
79
87
|
this.data[table] ||= {};
|
|
88
|
+
const isInsert = opt.saveMethod === 'insert';
|
|
89
|
+
const isUpdate = opt.saveMethod === 'update';
|
|
80
90
|
for (const r of rows) {
|
|
81
91
|
if (!r.id) {
|
|
82
92
|
this.cfg.logger.warn({ rows });
|
|
83
93
|
throw new Error(`InMemoryDB doesn't support id auto-generation in saveBatch, row without id was given`);
|
|
84
94
|
}
|
|
85
|
-
if (
|
|
95
|
+
if (isInsert && this.data[table][r.id]) {
|
|
86
96
|
throw new Error(`InMemoryDB: INSERT failed, entity exists: ${table}.${r.id}`);
|
|
87
97
|
}
|
|
88
|
-
if (
|
|
98
|
+
if (isUpdate && !this.data[table][r.id]) {
|
|
89
99
|
throw new Error(`InMemoryDB: UPDATE failed, entity doesn't exist: ${table}.${r.id}`);
|
|
90
100
|
}
|
|
91
101
|
// JSON parse/stringify (deep clone) is to:
|
|
@@ -94,6 +104,16 @@ export class InMemoryDB {
|
|
|
94
104
|
this.data[table][r.id] = JSON.parse(JSON.stringify(r), bufferReviver);
|
|
95
105
|
}
|
|
96
106
|
}
|
|
107
|
+
async multiSave(map, opt = {}) {
|
|
108
|
+
for (const [table, rows] of _stringMapEntries(map)) {
|
|
109
|
+
await this.saveBatch(table, rows, opt);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async patchById(_table, id, patch, _opt) {
|
|
113
|
+
const table = this.cfg.tablesPrefix + _table;
|
|
114
|
+
_assert(!this.data[table]?.[id], `InMemoryDB: patchById failed, entity doesn't exist: ${table}.${id}`);
|
|
115
|
+
Object.assign(this.data[table][id], patch);
|
|
116
|
+
}
|
|
97
117
|
async deleteByQuery(q, _opt) {
|
|
98
118
|
const table = this.cfg.tablesPrefix + q.table;
|
|
99
119
|
if (!this.data[table])
|
|
@@ -114,6 +134,13 @@ export class InMemoryDB {
|
|
|
114
134
|
}
|
|
115
135
|
return count;
|
|
116
136
|
}
|
|
137
|
+
async multiDelete(map, _opt) {
|
|
138
|
+
let count = 0;
|
|
139
|
+
for (const [table, ids] of _stringMapEntries(map)) {
|
|
140
|
+
count += await this.deleteByIds(table, ids, _opt);
|
|
141
|
+
}
|
|
142
|
+
return count;
|
|
143
|
+
}
|
|
117
144
|
async patchByQuery(q, patch) {
|
|
118
145
|
if (_isEmptyObject(patch))
|
|
119
146
|
return 0;
|
package/dist/query/dbQuery.d.ts
CHANGED
|
@@ -87,7 +87,7 @@ 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, ID = BM['id']> extends DBQuery<DBM> {
|
|
90
|
+
export declare class RunnableDBQuery<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID extends string = BM['id']> extends DBQuery<DBM> {
|
|
91
91
|
dao: CommonDao<BM, DBM, ID>;
|
|
92
92
|
/**
|
|
93
93
|
* Pass `table` to override table.
|
|
@@ -2,6 +2,7 @@ import type { JsonSchemaObject } from '@naturalcycles/js-lib/json-schema';
|
|
|
2
2
|
import type { BaseDBEntity } from '@naturalcycles/js-lib/types';
|
|
3
3
|
import { type ObjectSchema } from '@naturalcycles/nodejs-lib/joi';
|
|
4
4
|
export declare const TEST_TABLE = "TEST_TABLE";
|
|
5
|
+
export declare const TEST_TABLE_2 = "TEST_TABLE_2";
|
|
5
6
|
export interface TestItemBM extends BaseDBEntity {
|
|
6
7
|
k1: string;
|
|
7
8
|
k2?: string | null;
|
|
@@ -3,6 +3,7 @@ import { j } from '@naturalcycles/js-lib/json-schema';
|
|
|
3
3
|
import { baseDBEntitySchema, binarySchema, booleanSchema, numberSchema, objectSchema, stringSchema, } from '@naturalcycles/nodejs-lib/joi';
|
|
4
4
|
const MOCK_TS_2018_06_21 = 1529539200;
|
|
5
5
|
export const TEST_TABLE = 'TEST_TABLE';
|
|
6
|
+
export const TEST_TABLE_2 = 'TEST_TABLE_2';
|
|
6
7
|
export const testItemBMSchema = objectSchema({
|
|
7
8
|
k1: stringSchema,
|
|
8
9
|
k2: stringSchema.allow(null).optional(),
|
package/package.json
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import type { ValidationFunction } from '@naturalcycles/js-lib'
|
|
2
2
|
import type { AppError, ErrorMode } from '@naturalcycles/js-lib/error'
|
|
3
3
|
import type { CommonLogger } from '@naturalcycles/js-lib/log'
|
|
4
|
-
import type {
|
|
5
|
-
BaseDBEntity,
|
|
6
|
-
NumberOfMilliseconds,
|
|
7
|
-
Promisable,
|
|
8
|
-
UnixTimestamp,
|
|
9
|
-
} from '@naturalcycles/js-lib/types'
|
|
4
|
+
import type { BaseDBEntity, Promisable, UnixTimestamp } from '@naturalcycles/js-lib/types'
|
|
10
5
|
import type {
|
|
11
6
|
TransformLogProgressOptions,
|
|
12
7
|
TransformMapOptions,
|
|
@@ -403,9 +398,3 @@ export interface CommonDaoStreamOptions<IN>
|
|
|
403
398
|
}
|
|
404
399
|
|
|
405
400
|
export type CommonDaoCreateOptions = CommonDBCreateOptions
|
|
406
|
-
|
|
407
|
-
export interface OnValidationTimeData {
|
|
408
|
-
tookMillis: NumberOfMilliseconds
|
|
409
|
-
table: string
|
|
410
|
-
obj: any
|
|
411
|
-
}
|
|
@@ -14,13 +14,16 @@ import {
|
|
|
14
14
|
} from '@naturalcycles/js-lib/object/object.util.js'
|
|
15
15
|
import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'
|
|
16
16
|
import { _truncate } from '@naturalcycles/js-lib/string/string.util.js'
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
import {
|
|
18
|
+
_stringMapEntries,
|
|
19
|
+
_stringMapValues,
|
|
20
|
+
type AsyncIndexedMapper,
|
|
21
|
+
type BaseDBEntity,
|
|
22
|
+
type NonNegativeInteger,
|
|
23
|
+
type ObjectWithId,
|
|
24
|
+
type StringMap,
|
|
25
|
+
type UnixTimestampMillis,
|
|
26
|
+
type Unsaved,
|
|
24
27
|
} from '@naturalcycles/js-lib/types'
|
|
25
28
|
import { _passthroughPredicate, _typeCast, SKIP } from '@naturalcycles/js-lib/types'
|
|
26
29
|
import { stringId } from '@naturalcycles/nodejs-lib'
|
|
@@ -61,7 +64,11 @@ import { CommonDaoLogLevel } from './common.dao.model.js'
|
|
|
61
64
|
* BM = Backend model (optimized for API access)
|
|
62
65
|
* TM = Transport model (optimized to be sent over the wire)
|
|
63
66
|
*/
|
|
64
|
-
export class CommonDao<
|
|
67
|
+
export class CommonDao<
|
|
68
|
+
BM extends BaseDBEntity,
|
|
69
|
+
DBM extends BaseDBEntity = BM,
|
|
70
|
+
ID extends string = BM['id'],
|
|
71
|
+
> {
|
|
65
72
|
constructor(public cfg: CommonDaoCfg<BM, DBM, ID>) {
|
|
66
73
|
this.cfg = {
|
|
67
74
|
logLevel: CommonDaoLogLevel.NONE,
|
|
@@ -1257,6 +1264,159 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
|
|
|
1257
1264
|
await this.cfg.db.ping()
|
|
1258
1265
|
}
|
|
1259
1266
|
|
|
1267
|
+
withId(id: ID): DaoWithId<typeof this> {
|
|
1268
|
+
return {
|
|
1269
|
+
dao: this,
|
|
1270
|
+
id,
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
withIds(ids: ID[]): DaoWithIds<typeof this> {
|
|
1275
|
+
return {
|
|
1276
|
+
dao: this,
|
|
1277
|
+
ids,
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
toSave(input: BM | BM[]): DaoWithRows<typeof this> {
|
|
1282
|
+
return {
|
|
1283
|
+
dao: this,
|
|
1284
|
+
rows: [input].flat() as any[],
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Load rows (by their ids) from Multiple tables at once.
|
|
1290
|
+
* An optimized way to load data, minimizing DB round-trips.
|
|
1291
|
+
*
|
|
1292
|
+
* @experimental.
|
|
1293
|
+
*/
|
|
1294
|
+
static async multiGet<MAP extends Record<string, DaoWithIds<AnyDao> | DaoWithId<AnyDao>>>(
|
|
1295
|
+
inputMap: MAP,
|
|
1296
|
+
opt: CommonDaoReadOptions = {},
|
|
1297
|
+
): Promise<{
|
|
1298
|
+
[K in keyof MAP]: MAP[K] extends DaoWithIds<any>
|
|
1299
|
+
? InferBM<MAP[K]['dao']>[]
|
|
1300
|
+
: InferBM<MAP[K]['dao']> | null
|
|
1301
|
+
}> {
|
|
1302
|
+
const db = Object.values(inputMap)[0]?.dao.cfg.db
|
|
1303
|
+
if (!db) {
|
|
1304
|
+
return {} as any
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const idsByTable = CommonDao.prepareMultiGetIds(inputMap)
|
|
1308
|
+
|
|
1309
|
+
// todo: support tx
|
|
1310
|
+
const dbmsByTable = await db.multiGet(idsByTable, opt)
|
|
1311
|
+
|
|
1312
|
+
const dbmByTableById = CommonDao.multiGetMapByTableById(dbmsByTable)
|
|
1313
|
+
|
|
1314
|
+
return (await CommonDao.prepareMultiGetOutput(inputMap, dbmByTableById, opt)) as any
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
private static prepareMultiGetIds(
|
|
1318
|
+
inputMap: StringMap<DaoWithIds<AnyDao> | DaoWithId<AnyDao>>,
|
|
1319
|
+
): StringMap<string[]> {
|
|
1320
|
+
const idSetByTable: StringMap<Set<string>> = {}
|
|
1321
|
+
|
|
1322
|
+
for (const input of _stringMapValues(inputMap)) {
|
|
1323
|
+
const { table } = input.dao.cfg
|
|
1324
|
+
idSetByTable[table] ||= new Set()
|
|
1325
|
+
if ('id' in input) {
|
|
1326
|
+
// Singular
|
|
1327
|
+
idSetByTable[table].add(input.id)
|
|
1328
|
+
} else {
|
|
1329
|
+
// Plural
|
|
1330
|
+
for (const id of input.ids) {
|
|
1331
|
+
idSetByTable[table].add(id)
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const idsByTable: StringMap<string[]> = {}
|
|
1337
|
+
for (const [table, idSet] of _stringMapEntries(idSetByTable)) {
|
|
1338
|
+
idsByTable[table] = [...idSet]
|
|
1339
|
+
}
|
|
1340
|
+
return idsByTable
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
private static multiGetMapByTableById(
|
|
1344
|
+
dbmsByTable: StringMap<ObjectWithId[]>,
|
|
1345
|
+
): StringMap<StringMap<ObjectWithId>> {
|
|
1346
|
+
// We create this "map of maps", to be able to track the results back to the input props
|
|
1347
|
+
// This is needed to support:
|
|
1348
|
+
// - having multiple props from the same table
|
|
1349
|
+
const dbmByTableById: StringMap<StringMap<ObjectWithId>> = {}
|
|
1350
|
+
for (const [table, dbms] of _stringMapEntries(dbmsByTable)) {
|
|
1351
|
+
dbmByTableById[table] ||= {}
|
|
1352
|
+
for (const dbm of dbms) {
|
|
1353
|
+
dbmByTableById[table][dbm.id] = dbm
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
return dbmByTableById
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
private static async prepareMultiGetOutput(
|
|
1361
|
+
inputMap: StringMap<DaoWithIds<AnyDao> | DaoWithId<AnyDao>>,
|
|
1362
|
+
dbmByTableById: StringMap<StringMap<ObjectWithId>>,
|
|
1363
|
+
opt: CommonDaoReadOptions = {},
|
|
1364
|
+
): Promise<StringMap<unknown>> {
|
|
1365
|
+
const bmsByProp: StringMap<unknown> = {}
|
|
1366
|
+
|
|
1367
|
+
// Loop over input props again, to produce the output of the same shape as requested
|
|
1368
|
+
await pMap(_stringMapEntries(inputMap), async ([prop, input]) => {
|
|
1369
|
+
const { dao } = input
|
|
1370
|
+
const { table } = dao.cfg
|
|
1371
|
+
if ('id' in input) {
|
|
1372
|
+
// Singular
|
|
1373
|
+
const dbm = dbmByTableById[table]![input.id]
|
|
1374
|
+
bmsByProp[prop] = (await dao.dbmToBM(dbm, opt)) || null
|
|
1375
|
+
} else {
|
|
1376
|
+
// Plural
|
|
1377
|
+
// We apply filtering, to be able to support multiple input props fetching from the same table.
|
|
1378
|
+
// Without filtering - every prop will get ALL rows from that table.
|
|
1379
|
+
const dbms = input.ids.map(id => dbmByTableById[table]![id]).filter(_isTruthy)
|
|
1380
|
+
bmsByProp[prop] = await dao.dbmsToBM(dbms, opt)
|
|
1381
|
+
}
|
|
1382
|
+
})
|
|
1383
|
+
|
|
1384
|
+
return bmsByProp as any
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* Very @experimental.
|
|
1389
|
+
*/
|
|
1390
|
+
static async multiDeleteByIds(
|
|
1391
|
+
inputs: DaoWithIds<any>[],
|
|
1392
|
+
_opt: CommonDaoOptions = {},
|
|
1393
|
+
): Promise<NonNegativeInteger> {
|
|
1394
|
+
if (!inputs.length) return 0
|
|
1395
|
+
const { db } = inputs[0]!.dao.cfg
|
|
1396
|
+
const idsByTable: StringMap<string[]> = {}
|
|
1397
|
+
for (const { dao, ids } of inputs) {
|
|
1398
|
+
idsByTable[dao.cfg.table] = ids
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
return await db.multiDelete(idsByTable)
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
static async multiSave(
|
|
1405
|
+
inputs: DaoWithRows<any>[],
|
|
1406
|
+
opt: CommonDaoSaveBatchOptions<any> = {},
|
|
1407
|
+
): Promise<void> {
|
|
1408
|
+
if (!inputs.length) return
|
|
1409
|
+
const { db } = inputs[0]!.dao.cfg
|
|
1410
|
+
const dbmsByTable: StringMap<any[]> = {}
|
|
1411
|
+
await pMap(inputs, async ({ dao, rows }) => {
|
|
1412
|
+
const { table } = dao.cfg
|
|
1413
|
+
rows.forEach(bm => dao.assignIdCreatedUpdated(bm, opt))
|
|
1414
|
+
dbmsByTable[table] = await dao.bmsToDBM(rows, opt)
|
|
1415
|
+
})
|
|
1416
|
+
|
|
1417
|
+
await db.multiSave(dbmsByTable)
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1260
1420
|
async createTransaction(opt?: CommonDBTransactionOptions): Promise<CommonDaoTransaction> {
|
|
1261
1421
|
const tx = await this.cfg.db.createTransaction(opt)
|
|
1262
1422
|
return new CommonDaoTransaction(tx, this.cfg.logger!)
|
|
@@ -1395,7 +1555,7 @@ export class CommonDaoTransaction {
|
|
|
1395
1555
|
}
|
|
1396
1556
|
}
|
|
1397
1557
|
|
|
1398
|
-
async getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
|
|
1558
|
+
async getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
|
|
1399
1559
|
dao: CommonDao<BM, DBM, ID>,
|
|
1400
1560
|
id?: ID | null,
|
|
1401
1561
|
opt?: CommonDaoReadOptions,
|
|
@@ -1403,7 +1563,7 @@ export class CommonDaoTransaction {
|
|
|
1403
1563
|
return await dao.getById(id, { ...opt, tx: this.tx })
|
|
1404
1564
|
}
|
|
1405
1565
|
|
|
1406
|
-
async getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
|
|
1566
|
+
async getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
|
|
1407
1567
|
dao: CommonDao<BM, DBM, ID>,
|
|
1408
1568
|
ids: ID[],
|
|
1409
1569
|
opt?: CommonDaoReadOptions,
|
|
@@ -1448,7 +1608,7 @@ export class CommonDaoTransaction {
|
|
|
1448
1608
|
*
|
|
1449
1609
|
* So, this method is a rather simple convenience "Object.assign and then save".
|
|
1450
1610
|
*/
|
|
1451
|
-
async patch<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
|
|
1611
|
+
async patch<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
|
|
1452
1612
|
dao: CommonDao<BM, DBM, ID>,
|
|
1453
1613
|
bm: BM,
|
|
1454
1614
|
patch: Partial<BM>,
|
|
@@ -1459,20 +1619,42 @@ export class CommonDaoTransaction {
|
|
|
1459
1619
|
return await dao.save(bm, { ...opt, skipIfEquals, tx: this.tx })
|
|
1460
1620
|
}
|
|
1461
1621
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1622
|
+
// todo: use AnyDao/Infer in other methods as well, if this works well
|
|
1623
|
+
async deleteById<DAO extends AnyDao>(
|
|
1624
|
+
dao: DAO,
|
|
1625
|
+
id?: InferID<DAO> | null,
|
|
1465
1626
|
opt?: CommonDaoOptions,
|
|
1466
1627
|
): Promise<number> {
|
|
1467
1628
|
if (!id) return 0
|
|
1468
1629
|
return await this.deleteByIds(dao, [id], opt)
|
|
1469
1630
|
}
|
|
1470
1631
|
|
|
1471
|
-
async deleteByIds<
|
|
1472
|
-
dao:
|
|
1473
|
-
ids:
|
|
1632
|
+
async deleteByIds<DAO extends AnyDao>(
|
|
1633
|
+
dao: DAO,
|
|
1634
|
+
ids: InferID<DAO>[],
|
|
1474
1635
|
opt?: CommonDaoOptions,
|
|
1475
1636
|
): Promise<number> {
|
|
1476
1637
|
return await dao.deleteByIds(ids, { ...opt, tx: this.tx })
|
|
1477
1638
|
}
|
|
1478
1639
|
}
|
|
1640
|
+
|
|
1641
|
+
export interface DaoWithIds<DAO extends AnyDao> {
|
|
1642
|
+
dao: DAO
|
|
1643
|
+
ids: string[]
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
export interface DaoWithId<DAO extends AnyDao> {
|
|
1647
|
+
dao: DAO
|
|
1648
|
+
id: string
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
export interface DaoWithRows<DAO extends AnyDao> {
|
|
1652
|
+
dao: DAO
|
|
1653
|
+
rows: InferBM<DAO>[]
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
type InferBM<DAO> = DAO extends CommonDao<infer BM> ? BM : never
|
|
1657
|
+
// type InferDBM<DAO> = DAO extends CommonDao<any, infer DBM> ? DBM : never
|
|
1658
|
+
type InferID<DAO> = DAO extends CommonDao<any, any, infer ID> ? ID : never
|
|
1659
|
+
|
|
1660
|
+
export type AnyDao = CommonDao<any>
|
|
@@ -3,6 +3,7 @@ import type { ObjectWithId, StringMap } from '@naturalcycles/js-lib/types'
|
|
|
3
3
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
|
|
4
4
|
import type {
|
|
5
5
|
CommonDBOptions,
|
|
6
|
+
CommonDBReadOptions,
|
|
6
7
|
CommonDBSaveOptions,
|
|
7
8
|
CommonDBTransactionOptions,
|
|
8
9
|
DBTransaction,
|
|
@@ -60,6 +61,15 @@ export class BaseCommonDB implements CommonDB {
|
|
|
60
61
|
throw new Error('patchByQuery is not implemented')
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
async patchById<ROW extends ObjectWithId>(
|
|
65
|
+
_table: string,
|
|
66
|
+
_id: string,
|
|
67
|
+
_patch: Partial<ROW>,
|
|
68
|
+
_opt?: CommonDBOptions,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
throw new Error('patchById is not implemented')
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
async runQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<RunQueryResult<ROW>> {
|
|
64
74
|
throw new Error('runQuery is not implemented')
|
|
65
75
|
}
|
|
@@ -102,4 +112,22 @@ export class BaseCommonDB implements CommonDB {
|
|
|
102
112
|
): Promise<StringMap<number>> {
|
|
103
113
|
throw new Error('incrementBatch is not implemented')
|
|
104
114
|
}
|
|
115
|
+
|
|
116
|
+
async multiGet<ROW extends ObjectWithId>(
|
|
117
|
+
_map: StringMap<string[]>,
|
|
118
|
+
_opt?: CommonDBReadOptions,
|
|
119
|
+
): Promise<StringMap<ROW[]>> {
|
|
120
|
+
throw new Error('multiGetByIds is not implemented')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async multiSave<ROW extends ObjectWithId>(
|
|
124
|
+
_map: StringMap<ROW[]>,
|
|
125
|
+
_opt?: CommonDBSaveOptions<ROW>,
|
|
126
|
+
): Promise<void> {
|
|
127
|
+
throw new Error('multiSaveBatch is not implemented')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async multiDelete(_map: StringMap<string[]>, _opt?: CommonDBOptions): Promise<number> {
|
|
131
|
+
throw new Error('multiDeleteByIds is not implemented')
|
|
132
|
+
}
|
|
105
133
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib/json-schema'
|
|
2
|
-
import type { ObjectWithId, StringMap } from '@naturalcycles/js-lib/types'
|
|
2
|
+
import type { NonNegativeInteger, ObjectWithId, StringMap } from '@naturalcycles/js-lib/types'
|
|
3
3
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
|
|
4
4
|
import type {
|
|
5
5
|
CommonDBCreateOptions,
|
|
@@ -14,11 +14,6 @@ import type {
|
|
|
14
14
|
} from '../db.model.js'
|
|
15
15
|
import type { DBQuery } from '../query/dbQuery.js'
|
|
16
16
|
|
|
17
|
-
export enum CommonDBType {
|
|
18
|
-
'document' = 'document',
|
|
19
|
-
'relational' = 'relational',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
export interface CommonDB {
|
|
23
18
|
/**
|
|
24
19
|
* Relational databases are expected to return `null` for all missing properties.
|
|
@@ -72,6 +67,27 @@ export interface CommonDB {
|
|
|
72
67
|
opt?: CommonDBReadOptions,
|
|
73
68
|
) => Promise<ROW[]>
|
|
74
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Get rows from multiple tables at once.
|
|
72
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
73
|
+
*
|
|
74
|
+
* Takes `map`, which is a map from "table name" to an array of ids.
|
|
75
|
+
* Example:
|
|
76
|
+
* {
|
|
77
|
+
* 'TableOne': ['id1', 'id2'],
|
|
78
|
+
* 'TableTwo': ['id3'],
|
|
79
|
+
* }
|
|
80
|
+
*
|
|
81
|
+
* Returns a map with the same keys (table names) and arrays of rows as values.
|
|
82
|
+
* Even if some table is not found, it will return an empty array of results for that table.
|
|
83
|
+
*
|
|
84
|
+
* @experimental
|
|
85
|
+
*/
|
|
86
|
+
multiGet: <ROW extends ObjectWithId>(
|
|
87
|
+
idsByTable: StringMap<string[]>,
|
|
88
|
+
opt?: CommonDBReadOptions,
|
|
89
|
+
) => Promise<StringMap<ROW[]>>
|
|
90
|
+
|
|
75
91
|
// QUERY
|
|
76
92
|
/**
|
|
77
93
|
* Order by 'id' is not supported by all implementations (for example, Datastore doesn't support it).
|
|
@@ -84,7 +100,7 @@ export interface CommonDB {
|
|
|
84
100
|
runQueryCount: <ROW extends ObjectWithId>(
|
|
85
101
|
q: DBQuery<ROW>,
|
|
86
102
|
opt?: CommonDBReadOptions,
|
|
87
|
-
) => Promise<
|
|
103
|
+
) => Promise<NonNegativeInteger>
|
|
88
104
|
|
|
89
105
|
streamQuery: <ROW extends ObjectWithId>(
|
|
90
106
|
q: DBQuery<ROW>,
|
|
@@ -101,12 +117,66 @@ export interface CommonDB {
|
|
|
101
117
|
opt?: CommonDBSaveOptions<ROW>,
|
|
102
118
|
) => Promise<void>
|
|
103
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Save rows for multiple tables at once.
|
|
122
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
123
|
+
*
|
|
124
|
+
* Takes `map`, which is a map from "table name" to an array of rows.
|
|
125
|
+
* Example:
|
|
126
|
+
* {
|
|
127
|
+
* 'TableOne': [{ id: 'id1', ... }, { id: 'id2', ... }],
|
|
128
|
+
* 'TableTwo': [{ id: 'id3', ... }],
|
|
129
|
+
* }
|
|
130
|
+
*
|
|
131
|
+
* @experimental
|
|
132
|
+
*/
|
|
133
|
+
multiSave: <ROW extends ObjectWithId>(
|
|
134
|
+
rowsByTable: StringMap<ROW[]>,
|
|
135
|
+
opt?: CommonDBSaveOptions<ROW>,
|
|
136
|
+
) => Promise<void>
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Perform a partial update of a row by its id.
|
|
140
|
+
* Unlike save - doesn't require to first load the doc.
|
|
141
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
142
|
+
*
|
|
143
|
+
* The object with given id has to exist, otherwise an error will be thrown.
|
|
144
|
+
*
|
|
145
|
+
* @experimental
|
|
146
|
+
*/
|
|
147
|
+
patchById: <ROW extends ObjectWithId>(
|
|
148
|
+
table: string,
|
|
149
|
+
id: string,
|
|
150
|
+
patch: Partial<ROW>,
|
|
151
|
+
opt?: CommonDBOptions,
|
|
152
|
+
) => Promise<void>
|
|
153
|
+
|
|
104
154
|
// DELETE
|
|
105
155
|
/**
|
|
106
156
|
* Returns number of deleted items.
|
|
107
157
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
108
158
|
*/
|
|
109
|
-
deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<
|
|
159
|
+
deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<NonNegativeInteger>
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Deletes rows from multiple tables at once.
|
|
163
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
164
|
+
* Takes `map`, which is a map from "table name" to an array of ids to delete.
|
|
165
|
+
* Example:
|
|
166
|
+
* {
|
|
167
|
+
* 'TableOne': ['id1', 'id2'],
|
|
168
|
+
* 'TableTwo': ['id3'],
|
|
169
|
+
* }
|
|
170
|
+
*
|
|
171
|
+
* Returns number of deleted items.
|
|
172
|
+
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
173
|
+
*
|
|
174
|
+
* @experimental
|
|
175
|
+
*/
|
|
176
|
+
multiDelete: (
|
|
177
|
+
idsByTable: StringMap<string[]>,
|
|
178
|
+
opt?: CommonDBOptions,
|
|
179
|
+
) => Promise<NonNegativeInteger>
|
|
110
180
|
|
|
111
181
|
/**
|
|
112
182
|
* Returns number of deleted items.
|
|
@@ -115,7 +185,7 @@ export interface CommonDB {
|
|
|
115
185
|
deleteByQuery: <ROW extends ObjectWithId>(
|
|
116
186
|
q: DBQuery<ROW>,
|
|
117
187
|
opt?: CommonDBOptions,
|
|
118
|
-
) => Promise<
|
|
188
|
+
) => Promise<NonNegativeInteger>
|
|
119
189
|
|
|
120
190
|
/**
|
|
121
191
|
* Applies patch to all the rows that are matched by the query.
|
|
@@ -178,6 +248,11 @@ export interface CommonDB {
|
|
|
178
248
|
) => Promise<StringMap<number>>
|
|
179
249
|
}
|
|
180
250
|
|
|
251
|
+
export enum CommonDBType {
|
|
252
|
+
'document' = 'document',
|
|
253
|
+
'relational' = 'relational',
|
|
254
|
+
}
|
|
255
|
+
|
|
181
256
|
/**
|
|
182
257
|
* Manifest of supported features.
|
|
183
258
|
*/
|
|
@@ -190,6 +265,7 @@ export interface CommonDBSupport {
|
|
|
190
265
|
insertSaveMethod?: boolean
|
|
191
266
|
updateSaveMethod?: boolean
|
|
192
267
|
patchByQuery?: boolean
|
|
268
|
+
patchById?: boolean
|
|
193
269
|
increment?: boolean
|
|
194
270
|
createTable?: boolean
|
|
195
271
|
tableSchemas?: boolean
|
|
@@ -198,9 +274,10 @@ export interface CommonDBSupport {
|
|
|
198
274
|
nullValues?: boolean
|
|
199
275
|
transactions?: boolean
|
|
200
276
|
timeMachine?: boolean
|
|
277
|
+
multiTableOperations?: boolean
|
|
201
278
|
}
|
|
202
279
|
|
|
203
|
-
export const commonDBFullSupport: CommonDBSupport = {
|
|
280
|
+
export const commonDBFullSupport: Required<CommonDBSupport> = {
|
|
204
281
|
queries: true,
|
|
205
282
|
dbQueryFilter: true,
|
|
206
283
|
dbQueryFilterIn: true,
|
|
@@ -209,6 +286,7 @@ export const commonDBFullSupport: CommonDBSupport = {
|
|
|
209
286
|
insertSaveMethod: true,
|
|
210
287
|
updateSaveMethod: true,
|
|
211
288
|
patchByQuery: true,
|
|
289
|
+
patchById: true,
|
|
212
290
|
increment: true,
|
|
213
291
|
createTable: true,
|
|
214
292
|
tableSchemas: true,
|
|
@@ -217,4 +295,5 @@ export const commonDBFullSupport: CommonDBSupport = {
|
|
|
217
295
|
nullValues: true,
|
|
218
296
|
transactions: true,
|
|
219
297
|
timeMachine: true,
|
|
298
|
+
multiTableOperations: true,
|
|
220
299
|
}
|
|
@@ -145,6 +145,20 @@ export class InMemoryDB implements CommonDB {
|
|
|
145
145
|
return ids.map(id => this.data[table]![id] as ROW).filter(Boolean)
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
async multiGet<ROW extends ObjectWithId>(
|
|
149
|
+
map: StringMap<string[]>,
|
|
150
|
+
_opt: CommonDBOptions = {},
|
|
151
|
+
): Promise<StringMap<ROW[]>> {
|
|
152
|
+
const result: StringMap<ROW[]> = {}
|
|
153
|
+
|
|
154
|
+
for (const [tableName, ids] of _stringMapEntries(map)) {
|
|
155
|
+
const table = this.cfg.tablesPrefix + tableName
|
|
156
|
+
result[table] = ids.map(id => this.data[table]?.[id] as ROW).filter(Boolean)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result
|
|
160
|
+
}
|
|
161
|
+
|
|
148
162
|
async saveBatch<ROW extends ObjectWithId>(
|
|
149
163
|
_table: string,
|
|
150
164
|
rows: ROW[],
|
|
@@ -152,6 +166,8 @@ export class InMemoryDB implements CommonDB {
|
|
|
152
166
|
): Promise<void> {
|
|
153
167
|
const table = this.cfg.tablesPrefix + _table
|
|
154
168
|
this.data[table] ||= {}
|
|
169
|
+
const isInsert = opt.saveMethod === 'insert'
|
|
170
|
+
const isUpdate = opt.saveMethod === 'update'
|
|
155
171
|
|
|
156
172
|
for (const r of rows) {
|
|
157
173
|
if (!r.id) {
|
|
@@ -161,11 +177,11 @@ export class InMemoryDB implements CommonDB {
|
|
|
161
177
|
)
|
|
162
178
|
}
|
|
163
179
|
|
|
164
|
-
if (
|
|
180
|
+
if (isInsert && this.data[table][r.id]) {
|
|
165
181
|
throw new Error(`InMemoryDB: INSERT failed, entity exists: ${table}.${r.id}`)
|
|
166
182
|
}
|
|
167
183
|
|
|
168
|
-
if (
|
|
184
|
+
if (isUpdate && !this.data[table][r.id]) {
|
|
169
185
|
throw new Error(`InMemoryDB: UPDATE failed, entity doesn't exist: ${table}.${r.id}`)
|
|
170
186
|
}
|
|
171
187
|
|
|
@@ -176,6 +192,29 @@ export class InMemoryDB implements CommonDB {
|
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
194
|
|
|
195
|
+
async multiSave<ROW extends ObjectWithId>(
|
|
196
|
+
map: StringMap<ROW[]>,
|
|
197
|
+
opt: CommonDBSaveOptions<ROW> = {},
|
|
198
|
+
): Promise<void> {
|
|
199
|
+
for (const [table, rows] of _stringMapEntries(map)) {
|
|
200
|
+
await this.saveBatch(table, rows, opt)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async patchById<ROW extends ObjectWithId>(
|
|
205
|
+
_table: string,
|
|
206
|
+
id: string,
|
|
207
|
+
patch: Partial<ROW>,
|
|
208
|
+
_opt?: CommonDBOptions,
|
|
209
|
+
): Promise<void> {
|
|
210
|
+
const table = this.cfg.tablesPrefix + _table
|
|
211
|
+
_assert(
|
|
212
|
+
!this.data[table]?.[id],
|
|
213
|
+
`InMemoryDB: patchById failed, entity doesn't exist: ${table}.${id}`,
|
|
214
|
+
)
|
|
215
|
+
Object.assign(this.data[table]![id]!, patch)
|
|
216
|
+
}
|
|
217
|
+
|
|
179
218
|
async deleteByQuery<ROW extends ObjectWithId>(
|
|
180
219
|
q: DBQuery<ROW>,
|
|
181
220
|
_opt?: CommonDBOptions,
|
|
@@ -200,6 +239,16 @@ export class InMemoryDB implements CommonDB {
|
|
|
200
239
|
return count
|
|
201
240
|
}
|
|
202
241
|
|
|
242
|
+
async multiDelete(map: StringMap<string[]>, _opt?: CommonDBOptions): Promise<number> {
|
|
243
|
+
let count = 0
|
|
244
|
+
|
|
245
|
+
for (const [table, ids] of _stringMapEntries(map)) {
|
|
246
|
+
count += await this.deleteByIds(table, ids, _opt)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return count
|
|
250
|
+
}
|
|
251
|
+
|
|
203
252
|
async patchByQuery<ROW extends ObjectWithId>(
|
|
204
253
|
q: DBQuery<ROW>,
|
|
205
254
|
patch: Partial<ROW>,
|
package/src/query/dbQuery.ts
CHANGED
|
@@ -236,7 +236,7 @@ export class DBQuery<ROW extends ObjectWithId> {
|
|
|
236
236
|
export class RunnableDBQuery<
|
|
237
237
|
BM extends BaseDBEntity,
|
|
238
238
|
DBM extends BaseDBEntity = BM,
|
|
239
|
-
ID = BM['id'],
|
|
239
|
+
ID extends string = BM['id'],
|
|
240
240
|
> extends DBQuery<DBM> {
|
|
241
241
|
/**
|
|
242
242
|
* Pass `table` to override table.
|