@naturalcycles/db-lib 10.17.1 → 10.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.
- package/dist/commondao/common.dao.d.ts +18 -2
- package/dist/commondao/common.dao.js +83 -0
- package/dist/commondao/common.dao.model.d.ts +14 -9
- 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 -5
- 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/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 +16 -9
- package/src/commondao/common.dao.ts +111 -0
- package/src/commondb/base.common.db.ts +28 -0
- package/src/commondb/common.db.ts +89 -5
- package/src/inmemory/inMemory.db.ts +51 -2
- package/src/testing/test.model.ts +1 -0
|
@@ -1,12 +1,12 @@
|
|
|
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 { AsyncIndexedMapper, BaseDBEntity, StringMap, UnixTimestampMillis, Unsaved } from '@naturalcycles/js-lib/types';
|
|
4
|
+
import type { AsyncIndexedMapper, BaseDBEntity, NonNegativeInteger, StringMap, UnixTimestampMillis, 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';
|
|
8
8
|
import { RunnableDBQuery } from '../query/dbQuery.js';
|
|
9
|
-
import type { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoPatchByIdOptions, CommonDaoPatchOptions, CommonDaoReadOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model.js';
|
|
9
|
+
import type { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoPatchByIdOptions, CommonDaoPatchOptions, CommonDaoReadOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions, DaoWithId, DaoWithIds, DaoWithRows } from './common.dao.model.js';
|
|
10
10
|
/**
|
|
11
11
|
* Lowest common denominator API between supported Databases.
|
|
12
12
|
*
|
|
@@ -167,6 +167,22 @@ 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
|
+
id(id: string): DaoWithId;
|
|
171
|
+
ids(ids: string[]): DaoWithIds;
|
|
172
|
+
rows(rows: BM[]): DaoWithRows;
|
|
173
|
+
/**
|
|
174
|
+
* Very @experimental.
|
|
175
|
+
*/
|
|
176
|
+
static multiGetById<T>(inputs: DaoWithId[], opt?: CommonDaoReadOptions): Promise<T>;
|
|
177
|
+
/**
|
|
178
|
+
* Very @experimental.
|
|
179
|
+
*/
|
|
180
|
+
static multiGetByIds(inputs: DaoWithIds[], opt?: CommonDaoReadOptions): Promise<StringMap<unknown[]>>;
|
|
181
|
+
/**
|
|
182
|
+
* Very @experimental.
|
|
183
|
+
*/
|
|
184
|
+
static multiDeleteByIds(inputs: DaoWithIds[], _opt?: CommonDaoOptions): Promise<NonNegativeInteger>;
|
|
185
|
+
static multiSaveBatch(inputs: DaoWithRows[], opt?: CommonDaoSaveBatchOptions<any>): Promise<void>;
|
|
170
186
|
createTransaction(opt?: CommonDBTransactionOptions): Promise<CommonDaoTransaction>;
|
|
171
187
|
runInTransaction<T = void>(fn: CommonDaoTransactionFn<T>, opt?: CommonDBTransactionOptions): Promise<T>;
|
|
172
188
|
/**
|
|
@@ -981,6 +981,89 @@ export class CommonDao {
|
|
|
981
981
|
async ping() {
|
|
982
982
|
await this.cfg.db.ping();
|
|
983
983
|
}
|
|
984
|
+
id(id) {
|
|
985
|
+
return {
|
|
986
|
+
dao: this,
|
|
987
|
+
id,
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
ids(ids) {
|
|
991
|
+
return {
|
|
992
|
+
dao: this,
|
|
993
|
+
ids,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
rows(rows) {
|
|
997
|
+
return {
|
|
998
|
+
dao: this,
|
|
999
|
+
rows,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Very @experimental.
|
|
1004
|
+
*/
|
|
1005
|
+
static async multiGetById(inputs, opt = {}) {
|
|
1006
|
+
const inputs2 = inputs.map(input => ({
|
|
1007
|
+
dao: input.dao,
|
|
1008
|
+
ids: [input.id],
|
|
1009
|
+
}));
|
|
1010
|
+
const rowsByTable = await CommonDao.multiGetByIds(inputs2, opt);
|
|
1011
|
+
const results = inputs.map(({ dao }) => {
|
|
1012
|
+
const { table } = dao.cfg;
|
|
1013
|
+
return rowsByTable[table]?.[0] || null;
|
|
1014
|
+
});
|
|
1015
|
+
return results;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Very @experimental.
|
|
1019
|
+
*/
|
|
1020
|
+
static async multiGetByIds(inputs, opt = {}) {
|
|
1021
|
+
if (!inputs.length)
|
|
1022
|
+
return {};
|
|
1023
|
+
const { db } = inputs[0].dao.cfg;
|
|
1024
|
+
const idsByTable = {};
|
|
1025
|
+
for (const { dao, ids } of inputs) {
|
|
1026
|
+
const { table } = dao.cfg;
|
|
1027
|
+
idsByTable[table] = ids;
|
|
1028
|
+
}
|
|
1029
|
+
// todo: support tx
|
|
1030
|
+
const dbmsByTable = await db.multiGetByIds(idsByTable, opt);
|
|
1031
|
+
const bmsByTable = {};
|
|
1032
|
+
await pMap(inputs, async ({ dao }) => {
|
|
1033
|
+
const { table } = dao.cfg;
|
|
1034
|
+
let dbms = dbmsByTable[table] || [];
|
|
1035
|
+
if (dao.cfg.hooks.afterLoad && dbms.length) {
|
|
1036
|
+
dbms = (await pMap(dbms, async (dbm) => await dao.cfg.hooks.afterLoad(dbm))).filter(_isTruthy);
|
|
1037
|
+
}
|
|
1038
|
+
bmsByTable[table] = await dao.dbmsToBM(dbms, opt);
|
|
1039
|
+
});
|
|
1040
|
+
return bmsByTable;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Very @experimental.
|
|
1044
|
+
*/
|
|
1045
|
+
static async multiDeleteByIds(inputs, _opt = {}) {
|
|
1046
|
+
if (!inputs.length)
|
|
1047
|
+
return 0;
|
|
1048
|
+
const { db } = inputs[0].dao.cfg;
|
|
1049
|
+
const idsByTable = {};
|
|
1050
|
+
for (const { dao, ids } of inputs) {
|
|
1051
|
+
idsByTable[dao.cfg.table] = ids;
|
|
1052
|
+
}
|
|
1053
|
+
return await db.multiDeleteByIds(idsByTable);
|
|
1054
|
+
}
|
|
1055
|
+
static async multiSaveBatch(inputs, opt = {}) {
|
|
1056
|
+
if (!inputs.length)
|
|
1057
|
+
return;
|
|
1058
|
+
const { db } = inputs[0].dao.cfg;
|
|
1059
|
+
const dbmsByTable = {};
|
|
1060
|
+
await pMap(inputs, async ({ dao, rows }) => {
|
|
1061
|
+
const { table } = dao.cfg;
|
|
1062
|
+
rows.forEach(bm => dao.assignIdCreatedUpdated(bm, opt));
|
|
1063
|
+
dbmsByTable[table] = await dao.bmsToDBM(rows, opt);
|
|
1064
|
+
});
|
|
1065
|
+
await db.multiSaveBatch(dbmsByTable);
|
|
1066
|
+
}
|
|
984
1067
|
async createTransaction(opt) {
|
|
985
1068
|
const tx = await this.cfg.db.createTransaction(opt);
|
|
986
1069
|
return new CommonDaoTransaction(tx, this.cfg.logger);
|
|
@@ -1,10 +1,11 @@
|
|
|
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, ObjectWithId, 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';
|
|
8
|
+
import type { CommonDao } from './common.dao.js';
|
|
8
9
|
export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']> {
|
|
9
10
|
/**
|
|
10
11
|
* Allows to override the id generation function.
|
|
@@ -197,10 +198,7 @@ export interface CommonDaoIndex<DBM extends BaseDBEntity> {
|
|
|
197
198
|
* Name of the property to index.
|
|
198
199
|
*/
|
|
199
200
|
name: keyof DBM;
|
|
200
|
-
|
|
201
|
-
* Defaults to ['asc']
|
|
202
|
-
*/
|
|
203
|
-
order?: CommonDaoIndexOrder[];
|
|
201
|
+
order: CommonDaoIndexOrder[];
|
|
204
202
|
}
|
|
205
203
|
export type CommonDaoIndexOrder = 'asc' | 'desc' | 'array-contains';
|
|
206
204
|
/**
|
|
@@ -335,8 +333,15 @@ export interface CommonDaoStreamOptions<IN> extends CommonDaoReadOptions, Transf
|
|
|
335
333
|
chunkConcurrency?: number;
|
|
336
334
|
}
|
|
337
335
|
export type CommonDaoCreateOptions = CommonDBCreateOptions;
|
|
338
|
-
export interface
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
336
|
+
export interface DaoWithIds {
|
|
337
|
+
dao: CommonDao<any>;
|
|
338
|
+
ids: string[];
|
|
339
|
+
}
|
|
340
|
+
export interface DaoWithId {
|
|
341
|
+
dao: CommonDao<any>;
|
|
342
|
+
id: string;
|
|
343
|
+
}
|
|
344
|
+
export interface DaoWithRows<ROW extends ObjectWithId = ObjectWithId> {
|
|
345
|
+
dao: CommonDao<any>;
|
|
346
|
+
rows: ROW[];
|
|
342
347
|
}
|
|
@@ -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
|
+
multiGetByIds<ROW extends ObjectWithId>(_map: StringMap<string[]>, _opt?: CommonDBReadOptions): Promise<StringMap<ROW[]>>;
|
|
32
|
+
multiSaveBatch<ROW extends ObjectWithId>(_map: StringMap<ROW[]>, _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
33
|
+
multiDeleteByIds(_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 multiGetByIds(_map, _opt) {
|
|
61
|
+
throw new Error('multiGetByIds is not implemented');
|
|
62
|
+
}
|
|
63
|
+
async multiSaveBatch(_map, _opt) {
|
|
64
|
+
throw new Error('multiSaveBatch is not implemented');
|
|
65
|
+
}
|
|
66
|
+
async multiDeleteByIds(_map, _opt) {
|
|
67
|
+
throw new Error('multiDeleteByIds is not implemented');
|
|
68
|
+
}
|
|
57
69
|
}
|
|
@@ -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 { CommonDBCreateOptions, CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBTransactionOptions, DBTransaction, DBTransactionFn, RunQueryResult } from '../db.model.js';
|
|
5
5
|
import type { DBQuery } from '../query/dbQuery.js';
|
|
@@ -7,6 +7,10 @@ export declare enum CommonDBType {
|
|
|
7
7
|
'document' = "document",
|
|
8
8
|
'relational' = "relational"
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* A tuple that contains the table name and the id.
|
|
12
|
+
*/
|
|
13
|
+
export type CommonDBKey = [table: string, id: string];
|
|
10
14
|
export interface CommonDB {
|
|
11
15
|
/**
|
|
12
16
|
* Relational databases are expected to return `null` for all missing properties.
|
|
@@ -44,26 +48,83 @@ export interface CommonDB {
|
|
|
44
48
|
* (Such limitation exists because Datastore doesn't support it).
|
|
45
49
|
*/
|
|
46
50
|
getByIds: <ROW extends ObjectWithId>(table: string, ids: string[], opt?: CommonDBReadOptions) => Promise<ROW[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Get rows from multiple tables at once.
|
|
53
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
54
|
+
*
|
|
55
|
+
* Takes `map`, which is a map from "table name" to an array of ids.
|
|
56
|
+
* Example:
|
|
57
|
+
* {
|
|
58
|
+
* 'TableOne': ['id1', 'id2'],
|
|
59
|
+
* 'TableTwo': ['id3'],
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* Returns a map with the same keys (table names) and arrays of rows as values.
|
|
63
|
+
* Even if some table is not found, it will return an empty array of results for that table.
|
|
64
|
+
*
|
|
65
|
+
* @experimental
|
|
66
|
+
*/
|
|
67
|
+
multiGetByIds: <ROW extends ObjectWithId>(idsByTable: StringMap<string[]>, opt?: CommonDBReadOptions) => Promise<StringMap<ROW[]>>;
|
|
47
68
|
/**
|
|
48
69
|
* Order by 'id' is not supported by all implementations (for example, Datastore doesn't support it).
|
|
49
70
|
*/
|
|
50
71
|
runQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBReadOptions) => Promise<RunQueryResult<ROW>>;
|
|
51
|
-
runQueryCount: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBReadOptions) => Promise<
|
|
72
|
+
runQueryCount: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBReadOptions) => Promise<NonNegativeInteger>;
|
|
52
73
|
streamQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBStreamOptions) => ReadableTyped<ROW>;
|
|
53
74
|
/**
|
|
54
75
|
* rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment).
|
|
55
76
|
*/
|
|
56
77
|
saveBatch: <ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>) => Promise<void>;
|
|
57
78
|
/**
|
|
79
|
+
* Save rows for multiple tables at once.
|
|
80
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
81
|
+
*
|
|
82
|
+
* Takes `map`, which is a map from "table name" to an array of rows.
|
|
83
|
+
* Example:
|
|
84
|
+
* {
|
|
85
|
+
* 'TableOne': [{ id: 'id1', ... }, { id: 'id2', ... }],
|
|
86
|
+
* 'TableTwo': [{ id: 'id3', ... }],
|
|
87
|
+
* }
|
|
88
|
+
*
|
|
89
|
+
* @experimental
|
|
90
|
+
*/
|
|
91
|
+
multiSaveBatch: <ROW extends ObjectWithId>(rowsByTable: StringMap<ROW[]>, opt?: CommonDBSaveOptions<ROW>) => Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Perform a partial update of a row by its id.
|
|
94
|
+
* Unlike save - doesn't require to first load the doc.
|
|
95
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
96
|
+
*
|
|
97
|
+
* The object with given id has to exist, otherwise an error will be thrown.
|
|
98
|
+
*
|
|
99
|
+
* @experimental
|
|
100
|
+
*/
|
|
101
|
+
patchById: <ROW extends ObjectWithId>(table: string, id: string, patch: Partial<ROW>, opt?: CommonDBOptions) => Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Returns number of deleted items.
|
|
104
|
+
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
105
|
+
*/
|
|
106
|
+
deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<NonNegativeInteger>;
|
|
107
|
+
/**
|
|
108
|
+
* Deletes rows from multiple tables at once.
|
|
109
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
110
|
+
* Takes `map`, which is a map from "table name" to an array of ids to delete.
|
|
111
|
+
* Example:
|
|
112
|
+
* {
|
|
113
|
+
* 'TableOne': ['id1', 'id2'],
|
|
114
|
+
* 'TableTwo': ['id3'],
|
|
115
|
+
* }
|
|
116
|
+
*
|
|
58
117
|
* Returns number of deleted items.
|
|
59
118
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
119
|
+
*
|
|
120
|
+
* @experimental
|
|
60
121
|
*/
|
|
61
|
-
|
|
122
|
+
multiDeleteByIds: (idsByTable: StringMap<string[]>, opt?: CommonDBOptions) => Promise<NonNegativeInteger>;
|
|
62
123
|
/**
|
|
63
124
|
* Returns number of deleted items.
|
|
64
125
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
65
126
|
*/
|
|
66
|
-
deleteByQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions) => Promise<
|
|
127
|
+
deleteByQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions) => Promise<NonNegativeInteger>;
|
|
67
128
|
/**
|
|
68
129
|
* Applies patch to all the rows that are matched by the query.
|
|
69
130
|
*
|
|
@@ -123,6 +184,7 @@ export interface CommonDBSupport {
|
|
|
123
184
|
insertSaveMethod?: boolean;
|
|
124
185
|
updateSaveMethod?: boolean;
|
|
125
186
|
patchByQuery?: boolean;
|
|
187
|
+
patchById?: boolean;
|
|
126
188
|
increment?: boolean;
|
|
127
189
|
createTable?: boolean;
|
|
128
190
|
tableSchemas?: boolean;
|
|
@@ -131,5 +193,6 @@ export interface CommonDBSupport {
|
|
|
131
193
|
nullValues?: boolean;
|
|
132
194
|
transactions?: boolean;
|
|
133
195
|
timeMachine?: boolean;
|
|
196
|
+
multiTableOperations?: boolean;
|
|
134
197
|
}
|
|
135
|
-
export declare const commonDBFullSupport: CommonDBSupport
|
|
198
|
+
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
|
+
multiGetByIds<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
|
+
multiSaveBatch<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
|
+
multiDeleteByIds(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 multiGetByIds(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 multiSaveBatch(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 multiDeleteByIds(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;
|
|
@@ -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
|
@@ -3,7 +3,7 @@ import type { AppError, ErrorMode } from '@naturalcycles/js-lib/error'
|
|
|
3
3
|
import type { CommonLogger } from '@naturalcycles/js-lib/log'
|
|
4
4
|
import type {
|
|
5
5
|
BaseDBEntity,
|
|
6
|
-
|
|
6
|
+
ObjectWithId,
|
|
7
7
|
Promisable,
|
|
8
8
|
UnixTimestamp,
|
|
9
9
|
} from '@naturalcycles/js-lib/types'
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
} from '@naturalcycles/nodejs-lib/stream'
|
|
14
14
|
import type { CommonDB } from '../commondb/common.db.js'
|
|
15
15
|
import type { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model.js'
|
|
16
|
+
import type { CommonDao } from './common.dao.js'
|
|
16
17
|
|
|
17
18
|
export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']> {
|
|
18
19
|
/**
|
|
@@ -239,10 +240,7 @@ export interface CommonDaoIndex<DBM extends BaseDBEntity> {
|
|
|
239
240
|
* Name of the property to index.
|
|
240
241
|
*/
|
|
241
242
|
name: keyof DBM
|
|
242
|
-
|
|
243
|
-
* Defaults to ['asc']
|
|
244
|
-
*/
|
|
245
|
-
order?: CommonDaoIndexOrder[]
|
|
243
|
+
order: CommonDaoIndexOrder[]
|
|
246
244
|
}
|
|
247
245
|
|
|
248
246
|
export type CommonDaoIndexOrder = 'asc' | 'desc' | 'array-contains'
|
|
@@ -407,8 +405,17 @@ export interface CommonDaoStreamOptions<IN>
|
|
|
407
405
|
|
|
408
406
|
export type CommonDaoCreateOptions = CommonDBCreateOptions
|
|
409
407
|
|
|
410
|
-
export interface
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
408
|
+
export interface DaoWithIds {
|
|
409
|
+
dao: CommonDao<any>
|
|
410
|
+
ids: string[]
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export interface DaoWithId {
|
|
414
|
+
dao: CommonDao<any>
|
|
415
|
+
id: string
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export interface DaoWithRows<ROW extends ObjectWithId = ObjectWithId> {
|
|
419
|
+
dao: CommonDao<any>
|
|
420
|
+
rows: ROW[]
|
|
414
421
|
}
|
|
@@ -17,6 +17,7 @@ import { _truncate } from '@naturalcycles/js-lib/string/string.util.js'
|
|
|
17
17
|
import type {
|
|
18
18
|
AsyncIndexedMapper,
|
|
19
19
|
BaseDBEntity,
|
|
20
|
+
NonNegativeInteger,
|
|
20
21
|
ObjectWithId,
|
|
21
22
|
StringMap,
|
|
22
23
|
UnixTimestampMillis,
|
|
@@ -51,6 +52,9 @@ import type {
|
|
|
51
52
|
CommonDaoStreamForEachOptions,
|
|
52
53
|
CommonDaoStreamOptions,
|
|
53
54
|
CommonDaoStreamSaveOptions,
|
|
55
|
+
DaoWithId,
|
|
56
|
+
DaoWithIds,
|
|
57
|
+
DaoWithRows,
|
|
54
58
|
} from './common.dao.model.js'
|
|
55
59
|
import { CommonDaoLogLevel } from './common.dao.model.js'
|
|
56
60
|
|
|
@@ -1257,6 +1261,113 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
|
|
|
1257
1261
|
await this.cfg.db.ping()
|
|
1258
1262
|
}
|
|
1259
1263
|
|
|
1264
|
+
id(id: string): DaoWithId {
|
|
1265
|
+
return {
|
|
1266
|
+
dao: this,
|
|
1267
|
+
id,
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
ids(ids: string[]): DaoWithIds {
|
|
1272
|
+
return {
|
|
1273
|
+
dao: this,
|
|
1274
|
+
ids,
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
rows(rows: BM[]): DaoWithRows {
|
|
1279
|
+
return {
|
|
1280
|
+
dao: this,
|
|
1281
|
+
rows,
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* Very @experimental.
|
|
1287
|
+
*/
|
|
1288
|
+
static async multiGetById<T>(inputs: DaoWithId[], opt: CommonDaoReadOptions = {}): Promise<T> {
|
|
1289
|
+
const inputs2 = inputs.map(input => ({
|
|
1290
|
+
dao: input.dao,
|
|
1291
|
+
ids: [input.id],
|
|
1292
|
+
}))
|
|
1293
|
+
|
|
1294
|
+
const rowsByTable = await CommonDao.multiGetByIds(inputs2, opt)
|
|
1295
|
+
const results: any[] = inputs.map(({ dao }) => {
|
|
1296
|
+
const { table } = dao.cfg
|
|
1297
|
+
return rowsByTable[table]?.[0] || null
|
|
1298
|
+
})
|
|
1299
|
+
|
|
1300
|
+
return results as T
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* Very @experimental.
|
|
1305
|
+
*/
|
|
1306
|
+
static async multiGetByIds(
|
|
1307
|
+
inputs: DaoWithIds[],
|
|
1308
|
+
opt: CommonDaoReadOptions = {},
|
|
1309
|
+
): Promise<StringMap<unknown[]>> {
|
|
1310
|
+
if (!inputs.length) return {}
|
|
1311
|
+
const { db } = inputs[0]!.dao.cfg
|
|
1312
|
+
const idsByTable: StringMap<string[]> = {}
|
|
1313
|
+
for (const { dao, ids } of inputs) {
|
|
1314
|
+
const { table } = dao.cfg
|
|
1315
|
+
idsByTable[table] = ids
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// todo: support tx
|
|
1319
|
+
const dbmsByTable = await db.multiGetByIds(idsByTable, opt)
|
|
1320
|
+
const bmsByTable: StringMap<unknown[]> = {}
|
|
1321
|
+
|
|
1322
|
+
await pMap(inputs, async ({ dao }) => {
|
|
1323
|
+
const { table } = dao.cfg
|
|
1324
|
+
let dbms = dbmsByTable[table] || []
|
|
1325
|
+
|
|
1326
|
+
if (dao.cfg.hooks!.afterLoad && dbms.length) {
|
|
1327
|
+
dbms = (await pMap(dbms, async dbm => await dao.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
1328
|
+
_isTruthy,
|
|
1329
|
+
)
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
bmsByTable[table] = await dao.dbmsToBM(dbms, opt)
|
|
1333
|
+
})
|
|
1334
|
+
|
|
1335
|
+
return bmsByTable
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Very @experimental.
|
|
1340
|
+
*/
|
|
1341
|
+
static async multiDeleteByIds(
|
|
1342
|
+
inputs: DaoWithIds[],
|
|
1343
|
+
_opt: CommonDaoOptions = {},
|
|
1344
|
+
): Promise<NonNegativeInteger> {
|
|
1345
|
+
if (!inputs.length) return 0
|
|
1346
|
+
const { db } = inputs[0]!.dao.cfg
|
|
1347
|
+
const idsByTable: StringMap<string[]> = {}
|
|
1348
|
+
for (const { dao, ids } of inputs) {
|
|
1349
|
+
idsByTable[dao.cfg.table] = ids
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
return await db.multiDeleteByIds(idsByTable)
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
static async multiSaveBatch(
|
|
1356
|
+
inputs: DaoWithRows[],
|
|
1357
|
+
opt: CommonDaoSaveBatchOptions<any> = {},
|
|
1358
|
+
): Promise<void> {
|
|
1359
|
+
if (!inputs.length) return
|
|
1360
|
+
const { db } = inputs[0]!.dao.cfg
|
|
1361
|
+
const dbmsByTable: StringMap<any[]> = {}
|
|
1362
|
+
await pMap(inputs, async ({ dao, rows }) => {
|
|
1363
|
+
const { table } = dao.cfg
|
|
1364
|
+
rows.forEach(bm => dao.assignIdCreatedUpdated(bm, opt))
|
|
1365
|
+
dbmsByTable[table] = await dao.bmsToDBM(rows, opt)
|
|
1366
|
+
})
|
|
1367
|
+
|
|
1368
|
+
await db.multiSaveBatch(dbmsByTable)
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1260
1371
|
async createTransaction(opt?: CommonDBTransactionOptions): Promise<CommonDaoTransaction> {
|
|
1261
1372
|
const tx = await this.cfg.db.createTransaction(opt)
|
|
1262
1373
|
return new CommonDaoTransaction(tx, this.cfg.logger!)
|
|
@@ -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 multiGetByIds<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 multiSaveBatch<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 multiDeleteByIds(_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,
|
|
@@ -19,6 +19,11 @@ export enum CommonDBType {
|
|
|
19
19
|
'relational' = 'relational',
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* A tuple that contains the table name and the id.
|
|
24
|
+
*/
|
|
25
|
+
export type CommonDBKey = [table: string, id: string]
|
|
26
|
+
|
|
22
27
|
export interface CommonDB {
|
|
23
28
|
/**
|
|
24
29
|
* Relational databases are expected to return `null` for all missing properties.
|
|
@@ -72,6 +77,27 @@ export interface CommonDB {
|
|
|
72
77
|
opt?: CommonDBReadOptions,
|
|
73
78
|
) => Promise<ROW[]>
|
|
74
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Get rows from multiple tables at once.
|
|
82
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
83
|
+
*
|
|
84
|
+
* Takes `map`, which is a map from "table name" to an array of ids.
|
|
85
|
+
* Example:
|
|
86
|
+
* {
|
|
87
|
+
* 'TableOne': ['id1', 'id2'],
|
|
88
|
+
* 'TableTwo': ['id3'],
|
|
89
|
+
* }
|
|
90
|
+
*
|
|
91
|
+
* Returns a map with the same keys (table names) and arrays of rows as values.
|
|
92
|
+
* Even if some table is not found, it will return an empty array of results for that table.
|
|
93
|
+
*
|
|
94
|
+
* @experimental
|
|
95
|
+
*/
|
|
96
|
+
multiGetByIds: <ROW extends ObjectWithId>(
|
|
97
|
+
idsByTable: StringMap<string[]>,
|
|
98
|
+
opt?: CommonDBReadOptions,
|
|
99
|
+
) => Promise<StringMap<ROW[]>>
|
|
100
|
+
|
|
75
101
|
// QUERY
|
|
76
102
|
/**
|
|
77
103
|
* Order by 'id' is not supported by all implementations (for example, Datastore doesn't support it).
|
|
@@ -84,7 +110,7 @@ export interface CommonDB {
|
|
|
84
110
|
runQueryCount: <ROW extends ObjectWithId>(
|
|
85
111
|
q: DBQuery<ROW>,
|
|
86
112
|
opt?: CommonDBReadOptions,
|
|
87
|
-
) => Promise<
|
|
113
|
+
) => Promise<NonNegativeInteger>
|
|
88
114
|
|
|
89
115
|
streamQuery: <ROW extends ObjectWithId>(
|
|
90
116
|
q: DBQuery<ROW>,
|
|
@@ -101,12 +127,66 @@ export interface CommonDB {
|
|
|
101
127
|
opt?: CommonDBSaveOptions<ROW>,
|
|
102
128
|
) => Promise<void>
|
|
103
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Save rows for multiple tables at once.
|
|
132
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
133
|
+
*
|
|
134
|
+
* Takes `map`, which is a map from "table name" to an array of rows.
|
|
135
|
+
* Example:
|
|
136
|
+
* {
|
|
137
|
+
* 'TableOne': [{ id: 'id1', ... }, { id: 'id2', ... }],
|
|
138
|
+
* 'TableTwo': [{ id: 'id3', ... }],
|
|
139
|
+
* }
|
|
140
|
+
*
|
|
141
|
+
* @experimental
|
|
142
|
+
*/
|
|
143
|
+
multiSaveBatch: <ROW extends ObjectWithId>(
|
|
144
|
+
rowsByTable: StringMap<ROW[]>,
|
|
145
|
+
opt?: CommonDBSaveOptions<ROW>,
|
|
146
|
+
) => Promise<void>
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Perform a partial update of a row by its id.
|
|
150
|
+
* Unlike save - doesn't require to first load the doc.
|
|
151
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
152
|
+
*
|
|
153
|
+
* The object with given id has to exist, otherwise an error will be thrown.
|
|
154
|
+
*
|
|
155
|
+
* @experimental
|
|
156
|
+
*/
|
|
157
|
+
patchById: <ROW extends ObjectWithId>(
|
|
158
|
+
table: string,
|
|
159
|
+
id: string,
|
|
160
|
+
patch: Partial<ROW>,
|
|
161
|
+
opt?: CommonDBOptions,
|
|
162
|
+
) => Promise<void>
|
|
163
|
+
|
|
104
164
|
// DELETE
|
|
105
165
|
/**
|
|
106
166
|
* Returns number of deleted items.
|
|
107
167
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
108
168
|
*/
|
|
109
|
-
deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<
|
|
169
|
+
deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<NonNegativeInteger>
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Deletes rows from multiple tables at once.
|
|
173
|
+
* Mimics the API of some NoSQL databases like Firestore.
|
|
174
|
+
* Takes `map`, which is a map from "table name" to an array of ids to delete.
|
|
175
|
+
* Example:
|
|
176
|
+
* {
|
|
177
|
+
* 'TableOne': ['id1', 'id2'],
|
|
178
|
+
* 'TableTwo': ['id3'],
|
|
179
|
+
* }
|
|
180
|
+
*
|
|
181
|
+
* Returns number of deleted items.
|
|
182
|
+
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
183
|
+
*
|
|
184
|
+
* @experimental
|
|
185
|
+
*/
|
|
186
|
+
multiDeleteByIds: (
|
|
187
|
+
idsByTable: StringMap<string[]>,
|
|
188
|
+
opt?: CommonDBOptions,
|
|
189
|
+
) => Promise<NonNegativeInteger>
|
|
110
190
|
|
|
111
191
|
/**
|
|
112
192
|
* Returns number of deleted items.
|
|
@@ -115,7 +195,7 @@ export interface CommonDB {
|
|
|
115
195
|
deleteByQuery: <ROW extends ObjectWithId>(
|
|
116
196
|
q: DBQuery<ROW>,
|
|
117
197
|
opt?: CommonDBOptions,
|
|
118
|
-
) => Promise<
|
|
198
|
+
) => Promise<NonNegativeInteger>
|
|
119
199
|
|
|
120
200
|
/**
|
|
121
201
|
* Applies patch to all the rows that are matched by the query.
|
|
@@ -190,6 +270,7 @@ export interface CommonDBSupport {
|
|
|
190
270
|
insertSaveMethod?: boolean
|
|
191
271
|
updateSaveMethod?: boolean
|
|
192
272
|
patchByQuery?: boolean
|
|
273
|
+
patchById?: boolean
|
|
193
274
|
increment?: boolean
|
|
194
275
|
createTable?: boolean
|
|
195
276
|
tableSchemas?: boolean
|
|
@@ -198,9 +279,10 @@ export interface CommonDBSupport {
|
|
|
198
279
|
nullValues?: boolean
|
|
199
280
|
transactions?: boolean
|
|
200
281
|
timeMachine?: boolean
|
|
282
|
+
multiTableOperations?: boolean
|
|
201
283
|
}
|
|
202
284
|
|
|
203
|
-
export const commonDBFullSupport: CommonDBSupport = {
|
|
285
|
+
export const commonDBFullSupport: Required<CommonDBSupport> = {
|
|
204
286
|
queries: true,
|
|
205
287
|
dbQueryFilter: true,
|
|
206
288
|
dbQueryFilterIn: true,
|
|
@@ -209,6 +291,7 @@ export const commonDBFullSupport: CommonDBSupport = {
|
|
|
209
291
|
insertSaveMethod: true,
|
|
210
292
|
updateSaveMethod: true,
|
|
211
293
|
patchByQuery: true,
|
|
294
|
+
patchById: true,
|
|
212
295
|
increment: true,
|
|
213
296
|
createTable: true,
|
|
214
297
|
tableSchemas: true,
|
|
@@ -217,4 +300,5 @@ export const commonDBFullSupport: CommonDBSupport = {
|
|
|
217
300
|
nullValues: true,
|
|
218
301
|
transactions: true,
|
|
219
302
|
timeMachine: true,
|
|
303
|
+
multiTableOperations: true,
|
|
220
304
|
}
|
|
@@ -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 multiGetByIds<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 multiSaveBatch<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 multiDeleteByIds(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>,
|