@naturalcycles/db-lib 8.25.0 → 8.28.1
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 +7 -13
- package/dist/commondao/common.dao.model.d.ts +1 -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 +3 -2
- package/src/adapter/inmemory/inMemoryKeyValueDB.ts +5 -0
- package/src/commondao/common.dao.model.ts +1 -1
- package/src/commondao/common.dao.ts +12 -15
- 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);
|
|
@@ -71,23 +71,17 @@ class CommonDao {
|
|
|
71
71
|
this.logResult(started, op, bm, table);
|
|
72
72
|
return bm || null;
|
|
73
73
|
}
|
|
74
|
-
async
|
|
74
|
+
async getByIdOrEmpty(id, part = {}, opt) {
|
|
75
75
|
const bm = await this.getById(id, opt);
|
|
76
76
|
if (bm)
|
|
77
77
|
return bm;
|
|
78
|
-
return this.create({ ...
|
|
78
|
+
return this.create({ ...part, id }, opt);
|
|
79
79
|
}
|
|
80
|
-
async
|
|
81
|
-
const bm = await this.getById(id, opt);
|
|
82
|
-
if (bm)
|
|
83
|
-
return bm;
|
|
84
|
-
return this.create({ id }, opt);
|
|
85
|
-
}
|
|
86
|
-
async getByIdAsDBMOrEmpty(id, opt) {
|
|
80
|
+
async getByIdAsDBMOrEmpty(id, part = {}, opt) {
|
|
87
81
|
const dbm = await this.getByIdAsDBM(id, opt);
|
|
88
82
|
if (dbm)
|
|
89
83
|
return dbm;
|
|
90
|
-
const bm = this.create({ id }, opt);
|
|
84
|
+
const bm = this.create({ ...part, id }, opt);
|
|
91
85
|
return await this.bmToDBM(bm, opt);
|
|
92
86
|
}
|
|
93
87
|
async getByIdAsDBM(id, opt = {}) {
|
|
@@ -458,7 +452,7 @@ class CommonDao {
|
|
|
458
452
|
*/
|
|
459
453
|
async patch(id, patch, opt = {}) {
|
|
460
454
|
return await this.save({
|
|
461
|
-
...(await this.
|
|
455
|
+
...(await this.getByIdOrEmpty(id, patch, opt)),
|
|
462
456
|
...patch,
|
|
463
457
|
}, opt);
|
|
464
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>>;
|
|
@@ -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
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"@naturalcycles/bench-lib": "^1.0.0",
|
|
14
14
|
"@naturalcycles/dev-lib": "^12.0.1",
|
|
15
15
|
"@types/node": "^17.0.0",
|
|
16
|
-
"jest": "^27.0.3"
|
|
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.1",
|
|
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>>
|
|
@@ -88,8 +88,8 @@ export class CommonDao<
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// CREATE
|
|
91
|
-
create(
|
|
92
|
-
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
|
|
93
93
|
bm = this.validateAndConvert(bm, this.cfg.bmSchema, DBModelType.BM, opt)
|
|
94
94
|
|
|
95
95
|
// If no SCHEMA - return as is
|
|
@@ -124,29 +124,26 @@ export class CommonDao<
|
|
|
124
124
|
return bm || null
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
async
|
|
127
|
+
async getByIdOrEmpty(
|
|
128
128
|
id: string,
|
|
129
|
-
|
|
129
|
+
part: Partial<BM> = {},
|
|
130
130
|
opt?: CommonDaoOptions,
|
|
131
131
|
): Promise<Saved<BM>> {
|
|
132
132
|
const bm = await this.getById(id, opt)
|
|
133
133
|
if (bm) return bm
|
|
134
134
|
|
|
135
|
-
return this.create({ ...
|
|
135
|
+
return this.create({ ...part, id }, opt)
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
async
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async getByIdAsDBMOrEmpty(id: string, opt?: CommonDaoOptions): Promise<DBM> {
|
|
138
|
+
async getByIdAsDBMOrEmpty(
|
|
139
|
+
id: string,
|
|
140
|
+
part: Partial<BM> = {},
|
|
141
|
+
opt?: CommonDaoOptions,
|
|
142
|
+
): Promise<DBM> {
|
|
146
143
|
const dbm = await this.getByIdAsDBM(id, opt)
|
|
147
144
|
if (dbm) return dbm
|
|
148
145
|
|
|
149
|
-
const bm: BM = this.create({ id }
|
|
146
|
+
const bm: BM = this.create({ ...part, id }, opt) as any
|
|
150
147
|
return await this.bmToDBM(bm, opt)
|
|
151
148
|
}
|
|
152
149
|
|
|
@@ -614,7 +611,7 @@ export class CommonDao<
|
|
|
614
611
|
): Promise<Saved<BM>> {
|
|
615
612
|
return await this.save(
|
|
616
613
|
{
|
|
617
|
-
...(await this.
|
|
614
|
+
...(await this.getByIdOrEmpty(id, patch, opt)),
|
|
618
615
|
...patch,
|
|
619
616
|
} as any,
|
|
620
617
|
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()
|