@naturalcycles/db-lib 8.24.4 → 8.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/inmemory/inMemoryKeyValueDB.d.ts +1 -0
- package/dist/adapter/inmemory/inMemoryKeyValueDB.js +5 -0
- package/dist/commondao/common.dao.d.ts +3 -4
- package/dist/commondao/common.dao.js +18 -14
- package/dist/commondao/common.dao.model.d.ts +8 -1
- package/dist/kv/commonKeyValueDB.d.ts +1 -0
- package/dist/kv/commonKeyValueDao.d.ts +7 -2
- package/dist/kv/commonKeyValueDao.js +25 -0
- package/dist/testing/dbTest.d.ts +5 -0
- package/dist/testing/dbTest.js +17 -15
- package/dist/testing/keyValueDBTest.js +6 -0
- package/package.json +4 -3
- package/src/adapter/inmemory/inMemoryKeyValueDB.ts +5 -0
- package/src/commondao/common.dao.model.ts +9 -1
- package/src/commondao/common.dao.ts +23 -19
- package/src/kv/commonKeyValueDB.ts +2 -0
- package/src/kv/commonKeyValueDao.ts +38 -6
- package/src/testing/dbTest.ts +24 -15
- package/src/testing/keyValueDBTest.ts +8 -0
|
@@ -17,4 +17,5 @@ export declare class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
17
17
|
streamIds(table: string, limit?: number): ReadableTyped<string>;
|
|
18
18
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
19
19
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
20
|
+
count(table: string): Promise<number>;
|
|
20
21
|
}
|
|
@@ -34,5 +34,10 @@ class InMemoryKeyValueDB {
|
|
|
34
34
|
streamEntries(table, limit) {
|
|
35
35
|
return stream_1.Readable.from(Object.entries(this.data[table] || {}).slice(0, limit));
|
|
36
36
|
}
|
|
37
|
+
async count(table) {
|
|
38
|
+
var _a;
|
|
39
|
+
(_a = this.data)[table] || (_a[table] = {});
|
|
40
|
+
return Object.keys(this.data[table]).length;
|
|
41
|
+
}
|
|
37
42
|
}
|
|
38
43
|
exports.InMemoryKeyValueDB = InMemoryKeyValueDB;
|
|
@@ -13,12 +13,11 @@ import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOp
|
|
|
13
13
|
export declare class CommonDao<BM extends Partial<ObjectWithId>, DBM extends ObjectWithId = Saved<BM>, TM = BM> {
|
|
14
14
|
cfg: CommonDaoCfg<BM, DBM, TM>;
|
|
15
15
|
constructor(cfg: CommonDaoCfg<BM, DBM, TM>);
|
|
16
|
-
create(
|
|
16
|
+
create(part: Partial<BM>, opt?: CommonDaoOptions): Saved<BM>;
|
|
17
17
|
getById(id: undefined, opt?: CommonDaoOptions): Promise<null>;
|
|
18
18
|
getById(id?: string, opt?: CommonDaoOptions): Promise<Saved<BM> | null>;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
getByIdAsDBMOrEmpty(id: string, opt?: CommonDaoOptions): Promise<DBM>;
|
|
19
|
+
getByIdOrEmpty(id: string, part: Partial<BM>, opt?: CommonDaoOptions): Promise<Saved<BM>>;
|
|
20
|
+
getByIdAsDBMOrEmpty(id: string, part: Partial<BM>, opt?: CommonDaoOptions): Promise<DBM>;
|
|
22
21
|
getByIdAsDBM(id: undefined, opt?: CommonDaoOptions): Promise<null>;
|
|
23
22
|
getByIdAsDBM(id?: string, opt?: CommonDaoOptions): Promise<DBM | null>;
|
|
24
23
|
getByIdAsTM(id: undefined, opt?: CommonDaoOptions): Promise<null>;
|
|
@@ -44,8 +44,8 @@ class CommonDao {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
// CREATE
|
|
47
|
-
create(
|
|
48
|
-
let bm = this.cfg.hooks.beforeCreate(
|
|
47
|
+
create(part, opt = {}) {
|
|
48
|
+
let bm = this.cfg.hooks.beforeCreate(part);
|
|
49
49
|
bm = this.validateAndConvert(bm, this.cfg.bmSchema, db_model_1.DBModelType.BM, opt);
|
|
50
50
|
// If no SCHEMA - return as is
|
|
51
51
|
return this.assignIdCreatedUpdated(bm, opt);
|
|
@@ -56,28 +56,32 @@ class CommonDao {
|
|
|
56
56
|
const op = `getById(${id})`;
|
|
57
57
|
const table = opt.table || this.cfg.table;
|
|
58
58
|
const started = this.logStarted(op, table);
|
|
59
|
-
|
|
59
|
+
let dbm;
|
|
60
|
+
if (opt.timeout) {
|
|
61
|
+
// todo: possibly remove it after debugging is done
|
|
62
|
+
dbm = (await (0, js_lib_1.pTimeout)(this.cfg.db.getByIds(table, [id]), {
|
|
63
|
+
timeout: opt.timeout,
|
|
64
|
+
name: `getById(${table})`,
|
|
65
|
+
}))[0];
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
dbm = (await this.cfg.db.getByIds(table, [id]))[0];
|
|
69
|
+
}
|
|
60
70
|
const bm = opt.raw ? dbm : await this.dbmToBM(dbm, opt);
|
|
61
71
|
this.logResult(started, op, bm, table);
|
|
62
72
|
return bm || null;
|
|
63
73
|
}
|
|
64
|
-
async
|
|
65
|
-
const bm = await this.getById(id, opt);
|
|
66
|
-
if (bm)
|
|
67
|
-
return bm;
|
|
68
|
-
return this.create({ ...bmToCreate, id }, opt);
|
|
69
|
-
}
|
|
70
|
-
async getByIdOrEmpty(id, opt) {
|
|
74
|
+
async getByIdOrEmpty(id, part, opt) {
|
|
71
75
|
const bm = await this.getById(id, opt);
|
|
72
76
|
if (bm)
|
|
73
77
|
return bm;
|
|
74
|
-
return this.create({ id }, opt);
|
|
78
|
+
return this.create({ ...part, id }, opt);
|
|
75
79
|
}
|
|
76
|
-
async getByIdAsDBMOrEmpty(id, opt) {
|
|
80
|
+
async getByIdAsDBMOrEmpty(id, part, opt) {
|
|
77
81
|
const dbm = await this.getByIdAsDBM(id, opt);
|
|
78
82
|
if (dbm)
|
|
79
83
|
return dbm;
|
|
80
|
-
const bm = this.create({ id }, opt);
|
|
84
|
+
const bm = this.create({ ...part, id }, opt);
|
|
81
85
|
return await this.bmToDBM(bm, opt);
|
|
82
86
|
}
|
|
83
87
|
async getByIdAsDBM(id, opt = {}) {
|
|
@@ -448,7 +452,7 @@ class CommonDao {
|
|
|
448
452
|
*/
|
|
449
453
|
async patch(id, patch, opt = {}) {
|
|
450
454
|
return await this.save({
|
|
451
|
-
...(await this.
|
|
455
|
+
...(await this.getByIdOrEmpty(id, patch, opt)),
|
|
452
456
|
...patch,
|
|
453
457
|
}, opt);
|
|
454
458
|
}
|
|
@@ -4,7 +4,7 @@ import { CommonDB } from '../common.db';
|
|
|
4
4
|
import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model';
|
|
5
5
|
export declare type CommonDaoCreateIdHook<BM, DBM> = (obj: DBM | BM) => string;
|
|
6
6
|
export declare type CommonDaoParseNaturalIdHook<DBM> = (id: string) => Partial<DBM>;
|
|
7
|
-
export declare type CommonDaoBeforeCreateHook<BM> = (bm: Partial<BM>) => BM
|
|
7
|
+
export declare type CommonDaoBeforeCreateHook<BM> = (bm: Partial<BM>) => Partial<BM>;
|
|
8
8
|
export declare type CommonDaoBeforeDBMValidateHook<DBM> = (dbm: Partial<DBM>) => Partial<DBM>;
|
|
9
9
|
export declare type CommonDaoBeforeDBMToBMHook<BM, DBM> = (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>;
|
|
10
10
|
export declare type CommonDaoBeforeBMToDBMHook<BM, DBM> = (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>;
|
|
@@ -135,6 +135,13 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
135
135
|
* Useful e.g in AirtableDB where you can have one Dao to control multiple tables.
|
|
136
136
|
*/
|
|
137
137
|
table?: string;
|
|
138
|
+
/**
|
|
139
|
+
* If set - wraps the method in `pTimeout` with a timeout of given number of milliseconds.
|
|
140
|
+
* Currently, it is only used to debug an ongoing GCP infra issue.
|
|
141
|
+
*
|
|
142
|
+
* @experimental
|
|
143
|
+
*/
|
|
144
|
+
timeout?: number;
|
|
138
145
|
}
|
|
139
146
|
/**
|
|
140
147
|
* All properties default to undefined.
|
|
@@ -28,4 +28,5 @@ export interface CommonKeyValueDB {
|
|
|
28
28
|
streamIds(table: string, limit?: number): ReadableTyped<string>;
|
|
29
29
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
30
30
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
31
|
+
count(table: string): Promise<number>;
|
|
31
32
|
}
|
|
@@ -21,8 +21,9 @@ export interface CommonKeyValueDaoCfg<T> {
|
|
|
21
21
|
*/
|
|
22
22
|
logStarted?: boolean;
|
|
23
23
|
hooks?: {
|
|
24
|
-
mapValueToBuffer(v: T)
|
|
25
|
-
mapBufferToValue(b: Buffer)
|
|
24
|
+
mapValueToBuffer?: (v: T) => Promise<Buffer>;
|
|
25
|
+
mapBufferToValue?: (b: Buffer) => Promise<T>;
|
|
26
|
+
beforeCreate?: (v: Partial<T>) => Partial<T>;
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
export declare class CommonKeyValueDao<T> {
|
|
@@ -30,11 +31,15 @@ export declare class CommonKeyValueDao<T> {
|
|
|
30
31
|
constructor(cfg: CommonKeyValueDaoCfg<T>);
|
|
31
32
|
ping(): Promise<void>;
|
|
32
33
|
createTable(opt?: CommonDBCreateOptions): Promise<void>;
|
|
34
|
+
create(input?: Partial<T>): T;
|
|
33
35
|
getById(id?: string): Promise<T | null>;
|
|
36
|
+
getByIdOrEmpty(id: string, part?: Partial<T>): Promise<T>;
|
|
37
|
+
patch(id: string, patch: Partial<T>): Promise<T>;
|
|
34
38
|
getByIds(ids: string[]): Promise<KeyValueTuple<string, T>[]>;
|
|
35
39
|
save(id: string, value: T): Promise<void>;
|
|
36
40
|
saveBatch(entries: KeyValueTuple<string, T>[]): Promise<void>;
|
|
37
41
|
deleteByIds(ids: string[]): Promise<void>;
|
|
42
|
+
deleteById(id: string): Promise<void>;
|
|
38
43
|
streamIds(limit?: number): ReadableTyped<string>;
|
|
39
44
|
streamValues(limit?: number): ReadableTyped<Buffer>;
|
|
40
45
|
streamEntries(limit?: number): ReadableTyped<KeyValueTuple<string, T>>;
|
|
@@ -15,12 +15,34 @@ class CommonKeyValueDao {
|
|
|
15
15
|
async createTable(opt = {}) {
|
|
16
16
|
await this.cfg.db.createTable(this.cfg.table, opt);
|
|
17
17
|
}
|
|
18
|
+
create(input = {}) {
|
|
19
|
+
return {
|
|
20
|
+
...this.cfg.hooks?.beforeCreate?.(input),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
18
23
|
async getById(id) {
|
|
19
24
|
if (!id)
|
|
20
25
|
return null;
|
|
21
26
|
const [r] = await this.getByIds([id]);
|
|
22
27
|
return r?.[1] || null;
|
|
23
28
|
}
|
|
29
|
+
async getByIdOrEmpty(id, part = {}) {
|
|
30
|
+
const [r] = await this.getByIds([id]);
|
|
31
|
+
if (r)
|
|
32
|
+
return r[1];
|
|
33
|
+
return {
|
|
34
|
+
...this.cfg.hooks?.beforeCreate?.({}),
|
|
35
|
+
...part,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async patch(id, patch) {
|
|
39
|
+
const v = {
|
|
40
|
+
...(await this.getByIdOrEmpty(id)),
|
|
41
|
+
...patch,
|
|
42
|
+
};
|
|
43
|
+
await this.save(id, v);
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
24
46
|
async getByIds(ids) {
|
|
25
47
|
const entries = await this.cfg.db.getByIds(this.cfg.table, ids);
|
|
26
48
|
if (!this.cfg.hooks?.mapBufferToValue)
|
|
@@ -49,6 +71,9 @@ class CommonKeyValueDao {
|
|
|
49
71
|
async deleteByIds(ids) {
|
|
50
72
|
await this.cfg.db.deleteByIds(this.cfg.table, ids);
|
|
51
73
|
}
|
|
74
|
+
async deleteById(id) {
|
|
75
|
+
await this.cfg.db.deleteByIds(this.cfg.table, [id]);
|
|
76
|
+
}
|
|
52
77
|
streamIds(limit) {
|
|
53
78
|
return this.cfg.db.streamIds(this.cfg.table, limit);
|
|
54
79
|
}
|
package/dist/testing/dbTest.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ export interface CommonDBImplementationFeatures {
|
|
|
18
18
|
streaming?: boolean;
|
|
19
19
|
bufferSupport?: boolean;
|
|
20
20
|
nullValues?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Set false for SQL (relational) databases,
|
|
23
|
+
* they will return `null` for all missing properties.
|
|
24
|
+
*/
|
|
25
|
+
documentDB?: boolean;
|
|
21
26
|
}
|
|
22
27
|
/**
|
|
23
28
|
* All options default to `false`.
|
package/dist/testing/dbTest.js
CHANGED
|
@@ -12,7 +12,7 @@ const test_util_1 = require("./test.util");
|
|
|
12
12
|
function runCommonDBTest(db, features = {}, quirks = {}) {
|
|
13
13
|
const { querying = true, tableSchemas = true, createTable = true, dbQueryFilter = true,
|
|
14
14
|
// dbQueryFilterIn = true,
|
|
15
|
-
dbQueryOrder = true, dbQuerySelectFields = true, streaming = true, strongConsistency = true, bufferSupport = true, nullValues = true, } = features;
|
|
15
|
+
dbQueryOrder = true, dbQuerySelectFields = true, streaming = true, strongConsistency = true, bufferSupport = true, nullValues = true, documentDB = true, } = features;
|
|
16
16
|
// const {
|
|
17
17
|
// allowExtraPropertiesInResponse,
|
|
18
18
|
// allowBooleansAsUndefined,
|
|
@@ -71,20 +71,22 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
|
|
|
71
71
|
expect(item3Loaded.k2).toBe(null);
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
74
|
+
if (documentDB) {
|
|
75
|
+
test('undefined values should not be saved/loaded', async () => {
|
|
76
|
+
const item3 = {
|
|
77
|
+
...(0, test_model_1.createTestItemDBM)(3),
|
|
78
|
+
k2: undefined,
|
|
79
|
+
};
|
|
80
|
+
(0, test_util_1.deepFreeze)(item3);
|
|
81
|
+
const expected = { ...item3 };
|
|
82
|
+
delete expected.k2;
|
|
83
|
+
await db.saveBatch(test_model_1.TEST_TABLE, [item3]);
|
|
84
|
+
const item3Loaded = (await db.getByIds(test_model_1.TEST_TABLE, [item3.id]))[0];
|
|
85
|
+
expectMatch([expected], [item3Loaded], quirks);
|
|
86
|
+
expect(item3Loaded.k2).toBe(undefined);
|
|
87
|
+
expect(Object.keys(item3Loaded)).not.toContain('k2');
|
|
88
|
+
});
|
|
89
|
+
}
|
|
88
90
|
test('saveBatch test items', async () => {
|
|
89
91
|
await db.saveBatch(test_model_1.TEST_TABLE, items);
|
|
90
92
|
});
|
|
@@ -20,12 +20,18 @@ function runCommonKeyValueDBTest(db) {
|
|
|
20
20
|
const results = await db.getByIds(test_model_1.TEST_TABLE, testIds);
|
|
21
21
|
expect(results).toEqual([]);
|
|
22
22
|
});
|
|
23
|
+
test('count should be 0', async () => {
|
|
24
|
+
expect(await db.count(test_model_1.TEST_TABLE)).toBe(0);
|
|
25
|
+
});
|
|
23
26
|
test('saveBatch, then getByIds', async () => {
|
|
24
27
|
await db.saveBatch(test_model_1.TEST_TABLE, testEntries);
|
|
25
28
|
const entries = await db.getByIds(test_model_1.TEST_TABLE, testIds);
|
|
26
29
|
(0, js_lib_1._sortBy)(entries, e => e[0], true);
|
|
27
30
|
expect(entries).toEqual(testEntries);
|
|
28
31
|
});
|
|
32
|
+
test('count should be 3', async () => {
|
|
33
|
+
expect(await db.count(test_model_1.TEST_TABLE)).toBe(3);
|
|
34
|
+
});
|
|
29
35
|
test('streamIds', async () => {
|
|
30
36
|
const ids = await (0, nodejs_lib_1.readableToArray)(db.streamIds(test_model_1.TEST_TABLE));
|
|
31
37
|
ids.sort();
|
package/package.json
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@naturalcycles/bench-lib": "^1.0.0",
|
|
14
14
|
"@naturalcycles/dev-lib": "^12.0.1",
|
|
15
|
-
"@types/node": "^
|
|
16
|
-
"jest": "^27.0.3"
|
|
15
|
+
"@types/node": "^17.0.0",
|
|
16
|
+
"jest": "^27.0.3",
|
|
17
|
+
"weak-napi": "^2.0.2"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist",
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=14.15"
|
|
44
45
|
},
|
|
45
|
-
"version": "8.
|
|
46
|
+
"version": "8.28.0",
|
|
46
47
|
"description": "Lowest Common Denominator API to supported Databases",
|
|
47
48
|
"keywords": [
|
|
48
49
|
"db",
|
|
@@ -42,4 +42,9 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
42
42
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
|
|
43
43
|
return Readable.from(Object.entries(this.data[table] || {}).slice(0, limit))
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
async count(table: string): Promise<number> {
|
|
47
|
+
this.data[table] ||= {}
|
|
48
|
+
return Object.keys(this.data[table]!).length
|
|
49
|
+
}
|
|
45
50
|
}
|
|
@@ -13,7 +13,7 @@ import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../
|
|
|
13
13
|
// Hook DBM, BM, TM types should follow this exact order
|
|
14
14
|
export type CommonDaoCreateIdHook<BM, DBM> = (obj: DBM | BM) => string
|
|
15
15
|
export type CommonDaoParseNaturalIdHook<DBM> = (id: string) => Partial<DBM>
|
|
16
|
-
export type CommonDaoBeforeCreateHook<BM> = (bm: Partial<BM>) => BM
|
|
16
|
+
export type CommonDaoBeforeCreateHook<BM> = (bm: Partial<BM>) => Partial<BM>
|
|
17
17
|
export type CommonDaoBeforeDBMValidateHook<DBM> = (dbm: Partial<DBM>) => Partial<DBM>
|
|
18
18
|
export type CommonDaoBeforeDBMToBMHook<BM, DBM> = (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>
|
|
19
19
|
export type CommonDaoBeforeBMToDBMHook<BM, DBM> = (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>
|
|
@@ -168,6 +168,14 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
168
168
|
* Useful e.g in AirtableDB where you can have one Dao to control multiple tables.
|
|
169
169
|
*/
|
|
170
170
|
table?: string
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* If set - wraps the method in `pTimeout` with a timeout of given number of milliseconds.
|
|
174
|
+
* Currently, it is only used to debug an ongoing GCP infra issue.
|
|
175
|
+
*
|
|
176
|
+
* @experimental
|
|
177
|
+
*/
|
|
178
|
+
timeout?: number
|
|
171
179
|
}
|
|
172
180
|
|
|
173
181
|
/**
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
JsonSchemaRootObject,
|
|
13
13
|
ObjectWithId,
|
|
14
14
|
pMap,
|
|
15
|
+
pTimeout,
|
|
15
16
|
Saved,
|
|
16
17
|
} from '@naturalcycles/js-lib'
|
|
17
18
|
import {
|
|
@@ -87,8 +88,8 @@ export class CommonDao<
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// CREATE
|
|
90
|
-
create(
|
|
91
|
-
let bm = this.cfg.hooks!.beforeCreate!(
|
|
91
|
+
create(part: Partial<BM>, opt: CommonDaoOptions = {}): Saved<BM> {
|
|
92
|
+
let bm = this.cfg.hooks!.beforeCreate!(part) as BM
|
|
92
93
|
bm = this.validateAndConvert(bm, this.cfg.bmSchema, DBModelType.BM, opt)
|
|
93
94
|
|
|
94
95
|
// If no SCHEMA - return as is
|
|
@@ -103,35 +104,38 @@ export class CommonDao<
|
|
|
103
104
|
const op = `getById(${id})`
|
|
104
105
|
const table = opt.table || this.cfg.table
|
|
105
106
|
const started = this.logStarted(op, table)
|
|
106
|
-
|
|
107
|
+
|
|
108
|
+
let dbm: DBM | undefined
|
|
109
|
+
|
|
110
|
+
if (opt.timeout) {
|
|
111
|
+
// todo: possibly remove it after debugging is done
|
|
112
|
+
dbm = (
|
|
113
|
+
await pTimeout(this.cfg.db.getByIds<DBM>(table, [id]), {
|
|
114
|
+
timeout: opt.timeout,
|
|
115
|
+
name: `getById(${table})`,
|
|
116
|
+
})
|
|
117
|
+
)[0]
|
|
118
|
+
} else {
|
|
119
|
+
dbm = (await this.cfg.db.getByIds<DBM>(table, [id]))[0]
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
const bm = opt.raw ? (dbm as any) : await this.dbmToBM(dbm, opt)
|
|
108
123
|
this.logResult(started, op, bm, table)
|
|
109
124
|
return bm || null
|
|
110
125
|
}
|
|
111
126
|
|
|
112
|
-
async
|
|
113
|
-
id: string,
|
|
114
|
-
bmToCreate: Partial<BM>,
|
|
115
|
-
opt?: CommonDaoOptions,
|
|
116
|
-
): Promise<Saved<BM>> {
|
|
117
|
-
const bm = await this.getById(id, opt)
|
|
118
|
-
if (bm) return bm
|
|
119
|
-
|
|
120
|
-
return this.create({ ...bmToCreate, id }, opt)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async getByIdOrEmpty(id: string, opt?: CommonDaoOptions): Promise<Saved<BM>> {
|
|
127
|
+
async getByIdOrEmpty(id: string, part: Partial<BM>, opt?: CommonDaoOptions): Promise<Saved<BM>> {
|
|
124
128
|
const bm = await this.getById(id, opt)
|
|
125
129
|
if (bm) return bm
|
|
126
130
|
|
|
127
|
-
return this.create({ id }
|
|
131
|
+
return this.create({ ...part, id }, opt)
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
async getByIdAsDBMOrEmpty(id: string, opt?: CommonDaoOptions): Promise<DBM> {
|
|
134
|
+
async getByIdAsDBMOrEmpty(id: string, part: Partial<BM>, opt?: CommonDaoOptions): Promise<DBM> {
|
|
131
135
|
const dbm = await this.getByIdAsDBM(id, opt)
|
|
132
136
|
if (dbm) return dbm
|
|
133
137
|
|
|
134
|
-
const bm: BM = this.create({ id }
|
|
138
|
+
const bm: BM = this.create({ ...part, id }, opt) as any
|
|
135
139
|
return await this.bmToDBM(bm, opt)
|
|
136
140
|
}
|
|
137
141
|
|
|
@@ -599,7 +603,7 @@ export class CommonDao<
|
|
|
599
603
|
): Promise<Saved<BM>> {
|
|
600
604
|
return await this.save(
|
|
601
605
|
{
|
|
602
|
-
...(await this.
|
|
606
|
+
...(await this.getByIdOrEmpty(id, patch, opt)),
|
|
603
607
|
...patch,
|
|
604
608
|
} as any,
|
|
605
609
|
opt,
|
|
@@ -34,4 +34,6 @@ export interface CommonKeyValueDB {
|
|
|
34
34
|
streamIds(table: string, limit?: number): ReadableTyped<string>
|
|
35
35
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>
|
|
36
36
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>
|
|
37
|
+
|
|
38
|
+
count(table: string): Promise<number>
|
|
37
39
|
}
|
|
@@ -26,8 +26,9 @@ export interface CommonKeyValueDaoCfg<T> {
|
|
|
26
26
|
logStarted?: boolean
|
|
27
27
|
|
|
28
28
|
hooks?: {
|
|
29
|
-
mapValueToBuffer(v: T)
|
|
30
|
-
mapBufferToValue(b: Buffer)
|
|
29
|
+
mapValueToBuffer?: (v: T) => Promise<Buffer>
|
|
30
|
+
mapBufferToValue?: (b: Buffer) => Promise<T>
|
|
31
|
+
beforeCreate?: (v: Partial<T>) => Partial<T>
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -45,19 +46,46 @@ export class CommonKeyValueDao<T> {
|
|
|
45
46
|
await this.cfg.db.createTable(this.cfg.table, opt)
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
create(input: Partial<T> = {}): T {
|
|
50
|
+
return {
|
|
51
|
+
...this.cfg.hooks?.beforeCreate?.(input),
|
|
52
|
+
} as T
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
async getById(id?: string): Promise<T | null> {
|
|
49
56
|
if (!id) return null
|
|
50
57
|
const [r] = await this.getByIds([id])
|
|
51
58
|
return r?.[1] || null
|
|
52
59
|
}
|
|
53
60
|
|
|
61
|
+
async getByIdOrEmpty(id: string, part: Partial<T> = {}): Promise<T> {
|
|
62
|
+
const [r] = await this.getByIds([id])
|
|
63
|
+
if (r) return r[1]
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
...this.cfg.hooks?.beforeCreate?.({}),
|
|
67
|
+
...part,
|
|
68
|
+
} as T
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async patch(id: string, patch: Partial<T>): Promise<T> {
|
|
72
|
+
const v: T = {
|
|
73
|
+
...(await this.getByIdOrEmpty(id)),
|
|
74
|
+
...patch,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await this.save(id, v)
|
|
78
|
+
|
|
79
|
+
return v
|
|
80
|
+
}
|
|
81
|
+
|
|
54
82
|
async getByIds(ids: string[]): Promise<KeyValueTuple<string, T>[]> {
|
|
55
83
|
const entries = await this.cfg.db.getByIds(this.cfg.table, ids)
|
|
56
84
|
if (!this.cfg.hooks?.mapBufferToValue) return entries as any
|
|
57
85
|
|
|
58
86
|
return await pMap(entries, async ([id, buf]) => [
|
|
59
87
|
id,
|
|
60
|
-
await this.cfg.hooks!.mapBufferToValue(buf),
|
|
88
|
+
await this.cfg.hooks!.mapBufferToValue!(buf),
|
|
61
89
|
])
|
|
62
90
|
}
|
|
63
91
|
|
|
@@ -73,7 +101,7 @@ export class CommonKeyValueDao<T> {
|
|
|
73
101
|
} else {
|
|
74
102
|
bufferEntries = await pMap(entries, async ([id, v]) => [
|
|
75
103
|
id,
|
|
76
|
-
await this.cfg.hooks!.mapValueToBuffer(v),
|
|
104
|
+
await this.cfg.hooks!.mapValueToBuffer!(v),
|
|
77
105
|
])
|
|
78
106
|
}
|
|
79
107
|
|
|
@@ -84,6 +112,10 @@ export class CommonKeyValueDao<T> {
|
|
|
84
112
|
await this.cfg.db.deleteByIds(this.cfg.table, ids)
|
|
85
113
|
}
|
|
86
114
|
|
|
115
|
+
async deleteById(id: string): Promise<void> {
|
|
116
|
+
await this.cfg.db.deleteByIds(this.cfg.table, [id])
|
|
117
|
+
}
|
|
118
|
+
|
|
87
119
|
streamIds(limit?: number): ReadableTyped<string> {
|
|
88
120
|
return this.cfg.db.streamIds(this.cfg.table, limit)
|
|
89
121
|
}
|
|
@@ -96,7 +128,7 @@ export class CommonKeyValueDao<T> {
|
|
|
96
128
|
// todo: consider it when readableMap supports `errorMode: SUPPRESS`
|
|
97
129
|
// readableMap(this.cfg.db.streamValues(this.cfg.table, limit), async buf => await this.cfg.hooks!.mapBufferToValue(buf))
|
|
98
130
|
return this.cfg.db.streamValues(this.cfg.table, limit).pipe(
|
|
99
|
-
transformMap(async buf => await this.cfg.hooks!.mapBufferToValue(buf), {
|
|
131
|
+
transformMap(async buf => await this.cfg.hooks!.mapBufferToValue!(buf), {
|
|
100
132
|
errorMode: ErrorMode.SUPPRESS, // cause .pipe cannot propagate errors
|
|
101
133
|
}),
|
|
102
134
|
)
|
|
@@ -108,7 +140,7 @@ export class CommonKeyValueDao<T> {
|
|
|
108
140
|
}
|
|
109
141
|
|
|
110
142
|
return this.cfg.db.streamEntries(this.cfg.table, limit).pipe(
|
|
111
|
-
transformMap(async ([id, buf]) => [id, await this.cfg.hooks!.mapBufferToValue(buf)], {
|
|
143
|
+
transformMap(async ([id, buf]) => [id, await this.cfg.hooks!.mapBufferToValue!(buf)], {
|
|
112
144
|
errorMode: ErrorMode.SUPPRESS, // cause .pipe cannot propagate errors
|
|
113
145
|
}),
|
|
114
146
|
)
|
package/src/testing/dbTest.ts
CHANGED
|
@@ -35,6 +35,12 @@ export interface CommonDBImplementationFeatures {
|
|
|
35
35
|
|
|
36
36
|
bufferSupport?: boolean
|
|
37
37
|
nullValues?: boolean
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set false for SQL (relational) databases,
|
|
41
|
+
* they will return `null` for all missing properties.
|
|
42
|
+
*/
|
|
43
|
+
documentDB?: boolean
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
/**
|
|
@@ -78,6 +84,7 @@ export function runCommonDBTest(
|
|
|
78
84
|
strongConsistency = true,
|
|
79
85
|
bufferSupport = true,
|
|
80
86
|
nullValues = true,
|
|
87
|
+
documentDB = true,
|
|
81
88
|
} = features
|
|
82
89
|
|
|
83
90
|
// const {
|
|
@@ -151,21 +158,23 @@ export function runCommonDBTest(
|
|
|
151
158
|
})
|
|
152
159
|
}
|
|
153
160
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
161
|
+
if (documentDB) {
|
|
162
|
+
test('undefined values should not be saved/loaded', async () => {
|
|
163
|
+
const item3 = {
|
|
164
|
+
...createTestItemDBM(3),
|
|
165
|
+
k2: undefined,
|
|
166
|
+
}
|
|
167
|
+
deepFreeze(item3)
|
|
168
|
+
const expected = { ...item3 }
|
|
169
|
+
delete expected.k2
|
|
170
|
+
|
|
171
|
+
await db.saveBatch(TEST_TABLE, [item3])
|
|
172
|
+
const item3Loaded = (await db.getByIds<TestItemDBM>(TEST_TABLE, [item3.id]))[0]!
|
|
173
|
+
expectMatch([expected], [item3Loaded], quirks)
|
|
174
|
+
expect(item3Loaded.k2).toBe(undefined)
|
|
175
|
+
expect(Object.keys(item3Loaded)).not.toContain('k2')
|
|
176
|
+
})
|
|
177
|
+
}
|
|
169
178
|
|
|
170
179
|
test('saveBatch test items', async () => {
|
|
171
180
|
await db.saveBatch(TEST_TABLE, items)
|
|
@@ -25,6 +25,10 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
|
|
|
25
25
|
expect(results).toEqual([])
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
+
test('count should be 0', async () => {
|
|
29
|
+
expect(await db.count(TEST_TABLE)).toBe(0)
|
|
30
|
+
})
|
|
31
|
+
|
|
28
32
|
test('saveBatch, then getByIds', async () => {
|
|
29
33
|
await db.saveBatch(TEST_TABLE, testEntries)
|
|
30
34
|
|
|
@@ -33,6 +37,10 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
|
|
|
33
37
|
expect(entries).toEqual(testEntries)
|
|
34
38
|
})
|
|
35
39
|
|
|
40
|
+
test('count should be 3', async () => {
|
|
41
|
+
expect(await db.count(TEST_TABLE)).toBe(3)
|
|
42
|
+
})
|
|
43
|
+
|
|
36
44
|
test('streamIds', async () => {
|
|
37
45
|
const ids = await readableToArray(db.streamIds(TEST_TABLE))
|
|
38
46
|
ids.sort()
|