@naturalcycles/db-lib 9.22.0 → 9.23.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 +3 -4
- package/dist/adapter/inmemory/inMemoryKeyValueDB.js +8 -20
- package/dist/kv/commonKeyValueDB.d.ts +9 -14
- package/dist/kv/commonKeyValueDao.d.ts +10 -20
- package/dist/kv/commonKeyValueDao.js +26 -54
- package/dist/kv/commonKeyValueDaoMemoCache.d.ts +2 -2
- package/dist/testing/keyValueDBTest.js +15 -12
- package/dist/testing/keyValueDaoTest.d.ts +2 -2
- package/dist/testing/keyValueDaoTest.js +31 -8
- package/package.json +1 -1
- package/src/adapter/inmemory/inMemoryKeyValueDB.ts +11 -26
- package/src/kv/commonKeyValueDB.ts +10 -15
- package/src/kv/commonKeyValueDao.ts +37 -82
- package/src/kv/commonKeyValueDaoMemoCache.ts +2 -2
- package/src/testing/keyValueDBTest.ts +17 -14
- package/src/testing/keyValueDaoTest.ts +37 -12
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StringMap } from '@naturalcycles/js-lib';
|
|
2
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
3
|
import { CommonDBCreateOptions } from '../../db.model';
|
|
4
|
-
import { CommonKeyValueDB, KeyValueDBTuple } from '../../kv/commonKeyValueDB';
|
|
4
|
+
import { CommonKeyValueDB, IncrementTuple, KeyValueDBTuple } from '../../kv/commonKeyValueDB';
|
|
5
5
|
export interface InMemoryKeyValueDBCfg {
|
|
6
6
|
}
|
|
7
7
|
export declare class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
@@ -11,7 +11,7 @@ export declare class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
11
11
|
count?: boolean;
|
|
12
12
|
increment?: boolean;
|
|
13
13
|
};
|
|
14
|
-
data: StringMap<StringMap<
|
|
14
|
+
data: StringMap<StringMap<any>>;
|
|
15
15
|
ping(): Promise<void>;
|
|
16
16
|
createTable(_table: string, _opt?: CommonDBCreateOptions): Promise<void>;
|
|
17
17
|
deleteByIds(table: string, ids: string[]): Promise<void>;
|
|
@@ -21,6 +21,5 @@ export declare class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
21
21
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
22
22
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
23
23
|
count(table: string): Promise<number>;
|
|
24
|
-
|
|
25
|
-
incrementBatch(table: string, incrementMap: StringMap<number>): Promise<StringMap<number>>;
|
|
24
|
+
incrementBatch(table: string, entries: IncrementTuple[]): Promise<IncrementTuple[]>;
|
|
26
25
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.InMemoryKeyValueDB = void 0;
|
|
4
4
|
const node_stream_1 = require("node:stream");
|
|
5
|
-
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
5
|
const commonKeyValueDB_1 = require("../../kv/commonKeyValueDB");
|
|
7
6
|
class InMemoryKeyValueDB {
|
|
8
7
|
constructor(cfg = {}) {
|
|
@@ -10,7 +9,7 @@ class InMemoryKeyValueDB {
|
|
|
10
9
|
this.support = {
|
|
11
10
|
...commonKeyValueDB_1.commonKeyValueDBFullSupport,
|
|
12
11
|
};
|
|
13
|
-
// data[table][id]
|
|
12
|
+
// data[table][id] => any (can be Buffer, or number)
|
|
14
13
|
this.data = {};
|
|
15
14
|
}
|
|
16
15
|
async ping() { }
|
|
@@ -19,14 +18,13 @@ class InMemoryKeyValueDB {
|
|
|
19
18
|
this.data[table] ||= {};
|
|
20
19
|
ids.forEach(id => delete this.data[table][id]);
|
|
21
20
|
}
|
|
22
|
-
// todo: but should we work with Tuples or Objects?
|
|
23
21
|
async getByIds(table, ids) {
|
|
24
22
|
this.data[table] ||= {};
|
|
25
23
|
return ids.map(id => [id, this.data[table][id]]).filter(e => e[1]);
|
|
26
24
|
}
|
|
27
25
|
async saveBatch(table, entries) {
|
|
28
26
|
this.data[table] ||= {};
|
|
29
|
-
entries.forEach(([id,
|
|
27
|
+
entries.forEach(([id, v]) => (this.data[table][id] = v));
|
|
30
28
|
}
|
|
31
29
|
streamIds(table, limit) {
|
|
32
30
|
return node_stream_1.Readable.from(Object.keys(this.data[table] || {}).slice(0, limit));
|
|
@@ -41,23 +39,13 @@ class InMemoryKeyValueDB {
|
|
|
41
39
|
this.data[table] ||= {};
|
|
42
40
|
return Object.keys(this.data[table]).length;
|
|
43
41
|
}
|
|
44
|
-
async
|
|
42
|
+
async incrementBatch(table, entries) {
|
|
45
43
|
this.data[table] ||= {};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
async incrementBatch(table, incrementMap) {
|
|
52
|
-
this.data[table] ||= {};
|
|
53
|
-
const result = {};
|
|
54
|
-
for (const [id, by] of (0, js_lib_1._stringMapEntries)(incrementMap)) {
|
|
55
|
-
const newValue = parseInt(this.data[table][id]?.toString() || '0') + by;
|
|
56
|
-
// todo: but should this.data store Buffer or number for incremented values?
|
|
57
|
-
this.data[table][id] = Buffer.from(String(newValue));
|
|
58
|
-
result[id] = newValue;
|
|
59
|
-
}
|
|
60
|
-
return result;
|
|
44
|
+
return entries.map(([id, by]) => {
|
|
45
|
+
const newValue = (this.data[table][id] || 0) + by;
|
|
46
|
+
this.data[table][id] = newValue;
|
|
47
|
+
return [id, newValue];
|
|
48
|
+
});
|
|
61
49
|
}
|
|
62
50
|
}
|
|
63
51
|
exports.InMemoryKeyValueDB = InMemoryKeyValueDB;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Integer, UnixTimestampNumber } from '@naturalcycles/js-lib';
|
|
2
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
3
|
import { CommonDBCreateOptions } from '../db.model';
|
|
4
4
|
/**
|
|
@@ -32,30 +32,25 @@ export interface CommonKeyValueDB {
|
|
|
32
32
|
streamValues: (table: string, limit?: number) => ReadableTyped<Buffer>;
|
|
33
33
|
streamEntries: (table: string, limit?: number) => ReadableTyped<KeyValueDBTuple>;
|
|
34
34
|
count: (table: string) => Promise<number>;
|
|
35
|
-
/**
|
|
36
|
-
* Increments the value of a key in a table by a given amount.
|
|
37
|
-
* Default increment is 1 when `by` is not provided.
|
|
38
|
-
*
|
|
39
|
-
* Returns the new value.
|
|
40
|
-
*
|
|
41
|
-
* @experimental
|
|
42
|
-
*/
|
|
43
|
-
increment: (table: string, id: string, by?: number) => Promise<number>;
|
|
44
35
|
/**
|
|
45
36
|
* Perform a batch of Increment operations.
|
|
46
|
-
* Given
|
|
37
|
+
* Given entries array, increment each key of it (1st index of the tuple) by the given amount (2nd index of the tuple).
|
|
47
38
|
*
|
|
48
39
|
* Example:
|
|
49
|
-
*
|
|
40
|
+
* [
|
|
41
|
+
* ['key1', 2],
|
|
42
|
+
* ['key2', 3],
|
|
43
|
+
* ]
|
|
50
44
|
* would increment `key1` by 2, and `key2` by 3.
|
|
51
45
|
*
|
|
52
|
-
* Returns the
|
|
46
|
+
* Returns the entries array with tuples of the same structure, with updated numbers.
|
|
53
47
|
*
|
|
54
48
|
* @experimental
|
|
55
49
|
*/
|
|
56
|
-
incrementBatch: (table: string,
|
|
50
|
+
incrementBatch: (table: string, entries: IncrementTuple[]) => Promise<IncrementTuple[]>;
|
|
57
51
|
}
|
|
58
52
|
export type KeyValueDBTuple = [key: string, value: Buffer];
|
|
53
|
+
export type IncrementTuple = [key: string, value: Integer];
|
|
59
54
|
export interface CommonKeyValueDBSaveBatchOptions {
|
|
60
55
|
/**
|
|
61
56
|
* If set (and if it's implemented by the driver) - will set expiry TTL for each key of the batch.
|
|
@@ -2,7 +2,7 @@ import { CommonLogger, KeyValueTuple } from '@naturalcycles/js-lib';
|
|
|
2
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
3
|
import { CommonDaoLogLevel } from '../commondao/common.dao.model';
|
|
4
4
|
import { CommonDBCreateOptions } from '../db.model';
|
|
5
|
-
import { CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, KeyValueDBTuple } from './commonKeyValueDB';
|
|
5
|
+
import { CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, IncrementTuple, KeyValueDBTuple } from './commonKeyValueDB';
|
|
6
6
|
export interface CommonKeyValueDaoCfg<V> {
|
|
7
7
|
db: CommonKeyValueDB;
|
|
8
8
|
table: string;
|
|
@@ -23,40 +23,29 @@ export interface CommonKeyValueDaoCfg<V> {
|
|
|
23
23
|
* @default false
|
|
24
24
|
*/
|
|
25
25
|
logStarted?: boolean;
|
|
26
|
-
|
|
27
|
-
mapValueToBuffer?: (v: V) => Promise<Buffer>;
|
|
28
|
-
mapBufferToValue?: (b: Buffer) => Promise<V>;
|
|
29
|
-
beforeCreate?: (v: Partial<V>) => Partial<V>;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Set to `true` to conveniently enable zipping+JSON.stringify
|
|
33
|
-
* (and unzipping+JSON.parse) of the Buffer value via hooks.
|
|
34
|
-
* Custom hooks will override these hooks (if provided).
|
|
35
|
-
*/
|
|
36
|
-
deflatedJsonValue?: boolean;
|
|
26
|
+
transformer?: CommonKeyValueDaoTransformer<V>;
|
|
37
27
|
}
|
|
38
28
|
export type CommonKeyValueDaoSaveOptions = CommonKeyValueDBSaveBatchOptions;
|
|
39
|
-
export
|
|
29
|
+
export interface CommonKeyValueDaoTransformer<V> {
|
|
30
|
+
valueToBuffer: (v: V) => Promise<Buffer>;
|
|
31
|
+
bufferToValue: (buf: Buffer) => Promise<V>;
|
|
32
|
+
}
|
|
33
|
+
export declare const commonKeyValueDaoDeflatedJsonTransformer: CommonKeyValueDaoTransformer<any>;
|
|
34
|
+
export declare class CommonKeyValueDao<K extends string = string, V = Buffer> {
|
|
40
35
|
constructor(cfg: CommonKeyValueDaoCfg<V>);
|
|
41
36
|
cfg: CommonKeyValueDaoCfg<V> & {
|
|
42
|
-
hooks: NonNullable<CommonKeyValueDaoCfg<V>['hooks']>;
|
|
43
37
|
logger: CommonLogger;
|
|
44
38
|
};
|
|
45
39
|
ping(): Promise<void>;
|
|
46
40
|
createTable(opt?: CommonDBCreateOptions): Promise<void>;
|
|
47
|
-
create(input?: Partial<V>): V;
|
|
48
41
|
getById(id?: K): Promise<V | null>;
|
|
49
42
|
getByIdAsBuffer(id?: K): Promise<Buffer | null>;
|
|
50
43
|
requireById(id: K): Promise<V>;
|
|
51
44
|
requireByIdAsBuffer(id: K): Promise<Buffer>;
|
|
52
|
-
getByIdOrEmpty(id: K, part?: Partial<V>): Promise<V>;
|
|
53
|
-
patch(id: K, patch: Partial<V>, opt?: CommonKeyValueDaoSaveOptions): Promise<V>;
|
|
54
45
|
getByIds(ids: K[]): Promise<KeyValueTuple<string, V>[]>;
|
|
55
|
-
getByIdsAsBuffer(ids: K[]): Promise<
|
|
46
|
+
getByIdsAsBuffer(ids: K[]): Promise<KeyValueDBTuple[]>;
|
|
56
47
|
save(id: K, value: V, opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
|
|
57
|
-
saveAsBuffer(id: K, value: Buffer, opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
|
|
58
48
|
saveBatch(entries: KeyValueTuple<K, V>[], opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
|
|
59
|
-
saveBatchAsBuffer(entries: KeyValueDBTuple[], opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
|
|
60
49
|
deleteByIds(ids: K[]): Promise<void>;
|
|
61
50
|
deleteById(id: K): Promise<void>;
|
|
62
51
|
streamIds(limit?: number): ReadableTyped<K>;
|
|
@@ -72,4 +61,5 @@ export declare class CommonKeyValueDao<V, K extends string = string> {
|
|
|
72
61
|
* Returns the new value of the field.
|
|
73
62
|
*/
|
|
74
63
|
increment(id: K, by?: number): Promise<number>;
|
|
64
|
+
incrementBatch(entries: IncrementTuple[]): Promise<IncrementTuple[]>;
|
|
75
65
|
}
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CommonKeyValueDao = void 0;
|
|
3
|
+
exports.CommonKeyValueDao = exports.commonKeyValueDaoDeflatedJsonTransformer = void 0;
|
|
4
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
5
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
|
+
exports.commonKeyValueDaoDeflatedJsonTransformer = {
|
|
7
|
+
valueToBuffer: async (v) => await (0, nodejs_lib_1.deflateString)(JSON.stringify(v)),
|
|
8
|
+
bufferToValue: async (buf) => JSON.parse(await (0, nodejs_lib_1.inflateToString)(buf)),
|
|
9
|
+
};
|
|
6
10
|
// todo: logging
|
|
7
11
|
// todo: readonly
|
|
8
12
|
class CommonKeyValueDao {
|
|
9
13
|
constructor(cfg) {
|
|
10
14
|
this.cfg = {
|
|
11
|
-
hooks: {},
|
|
12
15
|
logger: console,
|
|
13
16
|
...cfg,
|
|
14
17
|
};
|
|
15
|
-
if (cfg.deflatedJsonValue) {
|
|
16
|
-
this.cfg.hooks = {
|
|
17
|
-
mapValueToBuffer: async (v) => await (0, nodejs_lib_1.deflateString)(JSON.stringify(v)),
|
|
18
|
-
mapBufferToValue: async (buf) => JSON.parse(await (0, nodejs_lib_1.inflateToString)(buf)),
|
|
19
|
-
...cfg.hooks,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
18
|
}
|
|
23
19
|
async ping() {
|
|
24
20
|
await this.cfg.db.ping();
|
|
@@ -26,11 +22,6 @@ class CommonKeyValueDao {
|
|
|
26
22
|
async createTable(opt = {}) {
|
|
27
23
|
await this.cfg.db.createTable(this.cfg.table, opt);
|
|
28
24
|
}
|
|
29
|
-
create(input = {}) {
|
|
30
|
-
return {
|
|
31
|
-
...this.cfg.hooks.beforeCreate?.(input),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
25
|
async getById(id) {
|
|
35
26
|
if (!id)
|
|
36
27
|
return null;
|
|
@@ -65,54 +56,31 @@ class CommonKeyValueDao {
|
|
|
65
56
|
}
|
|
66
57
|
return r[1];
|
|
67
58
|
}
|
|
68
|
-
async getByIdOrEmpty(id, part = {}) {
|
|
69
|
-
const [r] = await this.getByIds([id]);
|
|
70
|
-
if (r)
|
|
71
|
-
return r[1];
|
|
72
|
-
return {
|
|
73
|
-
...this.cfg.hooks.beforeCreate?.({}),
|
|
74
|
-
...part,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
async patch(id, patch, opt) {
|
|
78
|
-
const v = {
|
|
79
|
-
...(await this.getByIdOrEmpty(id)),
|
|
80
|
-
...patch,
|
|
81
|
-
};
|
|
82
|
-
await this.save(id, v, opt);
|
|
83
|
-
return v;
|
|
84
|
-
}
|
|
85
59
|
async getByIds(ids) {
|
|
86
60
|
const entries = await this.cfg.db.getByIds(this.cfg.table, ids);
|
|
87
|
-
if (!this.cfg.
|
|
61
|
+
if (!this.cfg.transformer)
|
|
88
62
|
return entries;
|
|
89
|
-
return await (0, js_lib_1.pMap)(entries, async ([id,
|
|
63
|
+
return await (0, js_lib_1.pMap)(entries, async ([id, raw]) => [
|
|
90
64
|
id,
|
|
91
|
-
await this.cfg.
|
|
65
|
+
await this.cfg.transformer.bufferToValue(raw),
|
|
92
66
|
]);
|
|
93
67
|
}
|
|
94
68
|
async getByIdsAsBuffer(ids) {
|
|
95
|
-
return
|
|
69
|
+
return await this.cfg.db.getByIds(this.cfg.table, ids);
|
|
96
70
|
}
|
|
97
71
|
async save(id, value, opt) {
|
|
98
72
|
await this.saveBatch([[id, value]], opt);
|
|
99
73
|
}
|
|
100
|
-
async saveAsBuffer(id, value, opt) {
|
|
101
|
-
await this.cfg.db.saveBatch(this.cfg.table, [[id, value]], opt);
|
|
102
|
-
}
|
|
103
74
|
async saveBatch(entries, opt) {
|
|
104
|
-
const {
|
|
105
|
-
let
|
|
106
|
-
if (!
|
|
107
|
-
|
|
75
|
+
const { transformer } = this.cfg;
|
|
76
|
+
let rawEntries;
|
|
77
|
+
if (!transformer) {
|
|
78
|
+
rawEntries = entries;
|
|
108
79
|
}
|
|
109
80
|
else {
|
|
110
|
-
|
|
81
|
+
rawEntries = await (0, js_lib_1.pMap)(entries, async ([id, v]) => [id, await transformer.valueToBuffer(v)]);
|
|
111
82
|
}
|
|
112
|
-
await this.cfg.db.saveBatch(this.cfg.table,
|
|
113
|
-
}
|
|
114
|
-
async saveBatchAsBuffer(entries, opt) {
|
|
115
|
-
await this.cfg.db.saveBatch(this.cfg.table, entries, opt);
|
|
83
|
+
await this.cfg.db.saveBatch(this.cfg.table, rawEntries, opt);
|
|
116
84
|
}
|
|
117
85
|
async deleteByIds(ids) {
|
|
118
86
|
await this.cfg.db.deleteByIds(this.cfg.table, ids);
|
|
@@ -124,13 +92,13 @@ class CommonKeyValueDao {
|
|
|
124
92
|
return this.cfg.db.streamIds(this.cfg.table, limit);
|
|
125
93
|
}
|
|
126
94
|
streamValues(limit) {
|
|
127
|
-
const {
|
|
128
|
-
if (!
|
|
95
|
+
const { transformer } = this.cfg;
|
|
96
|
+
if (!transformer) {
|
|
129
97
|
return this.cfg.db.streamValues(this.cfg.table, limit);
|
|
130
98
|
}
|
|
131
99
|
return this.cfg.db.streamValues(this.cfg.table, limit).flatMap(async (buf) => {
|
|
132
100
|
try {
|
|
133
|
-
return [await
|
|
101
|
+
return [await transformer.bufferToValue(buf)];
|
|
134
102
|
}
|
|
135
103
|
catch (err) {
|
|
136
104
|
this.cfg.logger.error(err);
|
|
@@ -141,13 +109,13 @@ class CommonKeyValueDao {
|
|
|
141
109
|
});
|
|
142
110
|
}
|
|
143
111
|
streamEntries(limit) {
|
|
144
|
-
const {
|
|
145
|
-
if (!
|
|
112
|
+
const { transformer } = this.cfg;
|
|
113
|
+
if (!transformer) {
|
|
146
114
|
return this.cfg.db.streamEntries(this.cfg.table, limit);
|
|
147
115
|
}
|
|
148
116
|
return this.cfg.db.streamEntries(this.cfg.table, limit).flatMap(async ([id, buf]) => {
|
|
149
117
|
try {
|
|
150
|
-
return [[id, await
|
|
118
|
+
return [[id, await transformer.bufferToValue(buf)]];
|
|
151
119
|
}
|
|
152
120
|
catch (err) {
|
|
153
121
|
this.cfg.logger.error(err);
|
|
@@ -173,7 +141,11 @@ class CommonKeyValueDao {
|
|
|
173
141
|
* Returns the new value of the field.
|
|
174
142
|
*/
|
|
175
143
|
async increment(id, by = 1) {
|
|
176
|
-
|
|
144
|
+
const [t] = await this.cfg.db.incrementBatch(this.cfg.table, [[id, by]]);
|
|
145
|
+
return t[1];
|
|
146
|
+
}
|
|
147
|
+
async incrementBatch(entries) {
|
|
148
|
+
return await this.cfg.db.incrementBatch(this.cfg.table, entries);
|
|
177
149
|
}
|
|
178
150
|
}
|
|
179
151
|
exports.CommonKeyValueDao = CommonKeyValueDao;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AsyncMemoCache, MISS, NumberOfSeconds } from '@naturalcycles/js-lib';
|
|
2
2
|
import { CommonKeyValueDao } from './commonKeyValueDao';
|
|
3
3
|
export interface CommonKeyValueDaoMemoCacheCfg<VALUE> {
|
|
4
|
-
dao: CommonKeyValueDao<VALUE>;
|
|
4
|
+
dao: CommonKeyValueDao<string, VALUE>;
|
|
5
5
|
/**
|
|
6
6
|
* If set, every `set()` will set `expireAt` (TTL) option.
|
|
7
7
|
*/
|
|
@@ -15,7 +15,7 @@ export interface CommonKeyValueDaoMemoCacheCfg<VALUE> {
|
|
|
15
15
|
* Also, does not support .clear(), as it's more dangerous than useful to actually
|
|
16
16
|
* clear the whole table/cache.
|
|
17
17
|
*/
|
|
18
|
-
export declare class CommonKeyValueDaoMemoCache<VALUE
|
|
18
|
+
export declare class CommonKeyValueDaoMemoCache<VALUE> implements AsyncMemoCache<string, VALUE> {
|
|
19
19
|
private cfg;
|
|
20
20
|
constructor(cfg: CommonKeyValueDaoMemoCacheCfg<VALUE>);
|
|
21
21
|
get(k: string): Promise<VALUE | typeof MISS>;
|
|
@@ -4,7 +4,10 @@ exports.runCommonKeyValueDBTest = runCommonKeyValueDBTest;
|
|
|
4
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
5
|
const test_model_1 = require("./test.model");
|
|
6
6
|
const testIds = (0, js_lib_1._range)(1, 4).map(n => `id${n}`);
|
|
7
|
-
const testEntries = testIds.map(id => [
|
|
7
|
+
const testEntries = testIds.map(id => [
|
|
8
|
+
id,
|
|
9
|
+
Buffer.from(`${id}value`),
|
|
10
|
+
]);
|
|
8
11
|
function runCommonKeyValueDBTest(db) {
|
|
9
12
|
beforeAll(async () => {
|
|
10
13
|
// Tests in this suite are not isolated,
|
|
@@ -89,23 +92,23 @@ function runCommonKeyValueDBTest(db) {
|
|
|
89
92
|
const id = 'nonExistingField';
|
|
90
93
|
const id2 = 'nonExistingField2';
|
|
91
94
|
test('increment on a non-existing field should set the value to 1', async () => {
|
|
92
|
-
const result = await db.
|
|
93
|
-
expect(result).
|
|
95
|
+
const result = await db.incrementBatch(test_model_1.TEST_TABLE, [[id, 1]]);
|
|
96
|
+
expect(result).toEqual([[id, 1]]);
|
|
94
97
|
});
|
|
95
98
|
test('increment on a existing field should increase the value by one', async () => {
|
|
96
|
-
const result = await db.
|
|
97
|
-
expect(result).
|
|
99
|
+
const result = await db.incrementBatch(test_model_1.TEST_TABLE, [[id, 1]]);
|
|
100
|
+
expect(result).toEqual([[id, 2]]);
|
|
98
101
|
});
|
|
99
102
|
test('increment should increase the value by the specified amount', async () => {
|
|
100
|
-
const result = await db.
|
|
101
|
-
expect(result).
|
|
103
|
+
const result = await db.incrementBatch(test_model_1.TEST_TABLE, [[id, 2]]);
|
|
104
|
+
expect(result).toEqual([[id, 4]]);
|
|
102
105
|
});
|
|
103
106
|
test('increment 2 ids at the same time', async () => {
|
|
104
|
-
const result = await db.incrementBatch(test_model_1.TEST_TABLE,
|
|
105
|
-
[id
|
|
106
|
-
[id2
|
|
107
|
-
|
|
108
|
-
expect(result).toEqual({
|
|
107
|
+
const result = await db.incrementBatch(test_model_1.TEST_TABLE, [
|
|
108
|
+
[id, 1],
|
|
109
|
+
[id2, 2],
|
|
110
|
+
]);
|
|
111
|
+
expect(Object.fromEntries(result)).toEqual({
|
|
109
112
|
[id]: 5,
|
|
110
113
|
[id2]: 2,
|
|
111
114
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function runCommonKeyValueDaoTest(
|
|
1
|
+
import { CommonKeyValueDB } from '../kv/commonKeyValueDB';
|
|
2
|
+
export declare function runCommonKeyValueDaoTest(db: CommonKeyValueDB): void;
|
|
@@ -2,9 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runCommonKeyValueDaoTest = runCommonKeyValueDaoTest;
|
|
4
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
5
|
+
const commonKeyValueDao_1 = require("../kv/commonKeyValueDao");
|
|
6
|
+
const test_model_1 = require("./test.model");
|
|
7
|
+
const testItems = (0, test_model_1.createTestItemsBM)(4);
|
|
8
|
+
const testIds = testItems.map(e => e.id);
|
|
9
|
+
const testEntries = testItems.map(e => [e.id, e]);
|
|
10
|
+
function runCommonKeyValueDaoTest(db) {
|
|
11
|
+
const dao = new commonKeyValueDao_1.CommonKeyValueDao({
|
|
12
|
+
db,
|
|
13
|
+
table: test_model_1.TEST_TABLE,
|
|
14
|
+
// todo: make this test support "deflatedJson" transformer
|
|
15
|
+
});
|
|
16
|
+
const { support } = db;
|
|
8
17
|
beforeAll(async () => {
|
|
9
18
|
// Tests in this suite are not isolated,
|
|
10
19
|
// and failing tests can leave the DB in an unexpected state for other tests,
|
|
@@ -17,7 +26,6 @@ function runCommonKeyValueDaoTest(dao) {
|
|
|
17
26
|
const ids = await dao.streamIds().toArray();
|
|
18
27
|
await dao.deleteByIds(ids);
|
|
19
28
|
});
|
|
20
|
-
const { support } = dao.cfg.db;
|
|
21
29
|
test('ping', async () => {
|
|
22
30
|
await dao.ping();
|
|
23
31
|
});
|
|
@@ -34,8 +42,11 @@ function runCommonKeyValueDaoTest(dao) {
|
|
|
34
42
|
test('saveBatch, then getByIds', async () => {
|
|
35
43
|
await dao.saveBatch(testEntries);
|
|
36
44
|
const entries = await dao.getByIds(testIds);
|
|
45
|
+
// console.log(typeof entries[0]![1], entries[0]![1])
|
|
37
46
|
(0, js_lib_1._sortBy)(entries, e => e[0], true);
|
|
38
|
-
expect(entries).toEqual(testEntries);
|
|
47
|
+
expect(entries).toEqual(testEntries); // Jest doesn't allow to compare Buffers directly
|
|
48
|
+
// expect(entries.map(e => e[0])).toEqual(testEntries.map(e => e[0]))
|
|
49
|
+
// expect(entries.map(e => e[1].toString())).toEqual(testEntries.map(e => e[1].toString()))
|
|
39
50
|
});
|
|
40
51
|
test('streamIds', async () => {
|
|
41
52
|
const ids = await dao.streamIds().toArray();
|
|
@@ -77,17 +88,29 @@ function runCommonKeyValueDaoTest(dao) {
|
|
|
77
88
|
expect(results).toEqual([]);
|
|
78
89
|
});
|
|
79
90
|
if (support.increment) {
|
|
91
|
+
const id = 'nonExistingField';
|
|
92
|
+
const id2 = 'nonExistingField2';
|
|
80
93
|
test('increment on a non-existing field should set the value to 1', async () => {
|
|
81
|
-
const result = await dao.increment(
|
|
94
|
+
const result = await dao.increment(id);
|
|
82
95
|
expect(result).toBe(1);
|
|
83
96
|
});
|
|
84
97
|
test('increment on a existing field should increase the value by one', async () => {
|
|
85
|
-
const result = await dao.increment(
|
|
98
|
+
const result = await dao.increment(id);
|
|
86
99
|
expect(result).toBe(2);
|
|
87
100
|
});
|
|
88
101
|
test('increment should increase the value by the specified amount', async () => {
|
|
89
|
-
const result = await dao.increment(
|
|
102
|
+
const result = await dao.increment(id, 2);
|
|
90
103
|
expect(result).toBe(4);
|
|
91
104
|
});
|
|
105
|
+
test('increment 2 ids at the same time', async () => {
|
|
106
|
+
const result = await dao.incrementBatch([
|
|
107
|
+
[id, 1],
|
|
108
|
+
[id2, 2],
|
|
109
|
+
]);
|
|
110
|
+
expect(Object.fromEntries(result)).toEqual({
|
|
111
|
+
[id]: 5,
|
|
112
|
+
[id2]: 2,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
92
115
|
}
|
|
93
116
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Readable } from 'node:stream'
|
|
2
|
-
import {
|
|
2
|
+
import { StringMap } from '@naturalcycles/js-lib'
|
|
3
3
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
4
4
|
import { CommonDBCreateOptions } from '../../db.model'
|
|
5
5
|
import {
|
|
6
6
|
CommonKeyValueDB,
|
|
7
7
|
commonKeyValueDBFullSupport,
|
|
8
|
+
IncrementTuple,
|
|
8
9
|
KeyValueDBTuple,
|
|
9
10
|
} from '../../kv/commonKeyValueDB'
|
|
10
11
|
|
|
@@ -17,8 +18,8 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
17
18
|
...commonKeyValueDBFullSupport,
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
// data[table][id]
|
|
21
|
-
data: StringMap<StringMap<
|
|
21
|
+
// data[table][id] => any (can be Buffer, or number)
|
|
22
|
+
data: StringMap<StringMap<any>> = {}
|
|
22
23
|
|
|
23
24
|
async ping(): Promise<void> {}
|
|
24
25
|
|
|
@@ -29,7 +30,6 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
29
30
|
ids.forEach(id => delete this.data[table]![id])
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
// todo: but should we work with Tuples or Objects?
|
|
33
33
|
async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
|
|
34
34
|
this.data[table] ||= {}
|
|
35
35
|
return ids.map(id => [id, this.data[table]![id]!] as KeyValueDBTuple).filter(e => e[1])
|
|
@@ -37,7 +37,7 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
37
37
|
|
|
38
38
|
async saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void> {
|
|
39
39
|
this.data[table] ||= {}
|
|
40
|
-
entries.forEach(([id,
|
|
40
|
+
entries.forEach(([id, v]) => (this.data[table]![id] = v))
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
streamIds(table: string, limit?: number): ReadableTyped<string> {
|
|
@@ -57,28 +57,13 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
57
57
|
return Object.keys(this.data[table]).length
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
async
|
|
60
|
+
async incrementBatch(table: string, entries: IncrementTuple[]): Promise<IncrementTuple[]> {
|
|
61
61
|
this.data[table] ||= {}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async incrementBatch(table: string, incrementMap: StringMap<number>): Promise<StringMap<number>> {
|
|
71
|
-
this.data[table] ||= {}
|
|
72
|
-
|
|
73
|
-
const result: StringMap<number> = {}
|
|
74
|
-
|
|
75
|
-
for (const [id, by] of _stringMapEntries(incrementMap)) {
|
|
76
|
-
const newValue = parseInt(this.data[table][id]?.toString() || '0') + by
|
|
77
|
-
// todo: but should this.data store Buffer or number for incremented values?
|
|
78
|
-
this.data[table][id] = Buffer.from(String(newValue))
|
|
79
|
-
result[id] = newValue
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return result
|
|
63
|
+
return entries.map(([id, by]) => {
|
|
64
|
+
const newValue = ((this.data[table]![id] as number | undefined) || 0) + by
|
|
65
|
+
this.data[table]![id] = newValue
|
|
66
|
+
return [id, newValue]
|
|
67
|
+
})
|
|
83
68
|
}
|
|
84
69
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Integer, UnixTimestampNumber } from '@naturalcycles/js-lib'
|
|
2
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
3
3
|
import { CommonDBCreateOptions } from '../db.model'
|
|
4
4
|
|
|
@@ -45,33 +45,28 @@ export interface CommonKeyValueDB {
|
|
|
45
45
|
|
|
46
46
|
count: (table: string) => Promise<number>
|
|
47
47
|
|
|
48
|
-
/**
|
|
49
|
-
* Increments the value of a key in a table by a given amount.
|
|
50
|
-
* Default increment is 1 when `by` is not provided.
|
|
51
|
-
*
|
|
52
|
-
* Returns the new value.
|
|
53
|
-
*
|
|
54
|
-
* @experimental
|
|
55
|
-
*/
|
|
56
|
-
increment: (table: string, id: string, by?: number) => Promise<number>
|
|
57
|
-
|
|
58
48
|
/**
|
|
59
49
|
* Perform a batch of Increment operations.
|
|
60
|
-
* Given
|
|
50
|
+
* Given entries array, increment each key of it (1st index of the tuple) by the given amount (2nd index of the tuple).
|
|
61
51
|
*
|
|
62
52
|
* Example:
|
|
63
|
-
*
|
|
53
|
+
* [
|
|
54
|
+
* ['key1', 2],
|
|
55
|
+
* ['key2', 3],
|
|
56
|
+
* ]
|
|
64
57
|
* would increment `key1` by 2, and `key2` by 3.
|
|
65
58
|
*
|
|
66
|
-
* Returns the
|
|
59
|
+
* Returns the entries array with tuples of the same structure, with updated numbers.
|
|
67
60
|
*
|
|
68
61
|
* @experimental
|
|
69
62
|
*/
|
|
70
|
-
incrementBatch: (table: string,
|
|
63
|
+
incrementBatch: (table: string, entries: IncrementTuple[]) => Promise<IncrementTuple[]>
|
|
71
64
|
}
|
|
72
65
|
|
|
73
66
|
export type KeyValueDBTuple = [key: string, value: Buffer]
|
|
74
67
|
|
|
68
|
+
export type IncrementTuple = [key: string, value: Integer]
|
|
69
|
+
|
|
75
70
|
export interface CommonKeyValueDBSaveBatchOptions {
|
|
76
71
|
/**
|
|
77
72
|
* If set (and if it's implemented by the driver) - will set expiry TTL for each key of the batch.
|
|
@@ -5,6 +5,7 @@ import { CommonDBCreateOptions } from '../db.model'
|
|
|
5
5
|
import {
|
|
6
6
|
CommonKeyValueDB,
|
|
7
7
|
CommonKeyValueDBSaveBatchOptions,
|
|
8
|
+
IncrementTuple,
|
|
8
9
|
KeyValueDBTuple,
|
|
9
10
|
} from './commonKeyValueDB'
|
|
10
11
|
|
|
@@ -34,44 +35,33 @@ export interface CommonKeyValueDaoCfg<V> {
|
|
|
34
35
|
*/
|
|
35
36
|
logStarted?: boolean
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
mapValueToBuffer?: (v: V) => Promise<Buffer>
|
|
39
|
-
mapBufferToValue?: (b: Buffer) => Promise<V>
|
|
40
|
-
beforeCreate?: (v: Partial<V>) => Partial<V>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Set to `true` to conveniently enable zipping+JSON.stringify
|
|
45
|
-
* (and unzipping+JSON.parse) of the Buffer value via hooks.
|
|
46
|
-
* Custom hooks will override these hooks (if provided).
|
|
47
|
-
*/
|
|
48
|
-
deflatedJsonValue?: boolean
|
|
38
|
+
transformer?: CommonKeyValueDaoTransformer<V>
|
|
49
39
|
}
|
|
50
40
|
|
|
51
41
|
export type CommonKeyValueDaoSaveOptions = CommonKeyValueDBSaveBatchOptions
|
|
52
42
|
|
|
43
|
+
export interface CommonKeyValueDaoTransformer<V> {
|
|
44
|
+
valueToBuffer: (v: V) => Promise<Buffer>
|
|
45
|
+
bufferToValue: (buf: Buffer) => Promise<V>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const commonKeyValueDaoDeflatedJsonTransformer: CommonKeyValueDaoTransformer<any> = {
|
|
49
|
+
valueToBuffer: async v => await deflateString(JSON.stringify(v)),
|
|
50
|
+
bufferToValue: async buf => JSON.parse(await inflateToString(buf)),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
53
|
// todo: logging
|
|
54
54
|
// todo: readonly
|
|
55
55
|
|
|
56
|
-
export class CommonKeyValueDao<
|
|
56
|
+
export class CommonKeyValueDao<K extends string = string, V = Buffer> {
|
|
57
57
|
constructor(cfg: CommonKeyValueDaoCfg<V>) {
|
|
58
58
|
this.cfg = {
|
|
59
|
-
hooks: {},
|
|
60
59
|
logger: console,
|
|
61
60
|
...cfg,
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
if (cfg.deflatedJsonValue) {
|
|
65
|
-
this.cfg.hooks = {
|
|
66
|
-
mapValueToBuffer: async v => await deflateString(JSON.stringify(v)),
|
|
67
|
-
mapBufferToValue: async buf => JSON.parse(await inflateToString(buf)),
|
|
68
|
-
...cfg.hooks,
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
62
|
}
|
|
72
63
|
|
|
73
64
|
cfg: CommonKeyValueDaoCfg<V> & {
|
|
74
|
-
hooks: NonNullable<CommonKeyValueDaoCfg<V>['hooks']>
|
|
75
65
|
logger: CommonLogger
|
|
76
66
|
}
|
|
77
67
|
|
|
@@ -83,12 +73,6 @@ export class CommonKeyValueDao<V, K extends string = string> {
|
|
|
83
73
|
await this.cfg.db.createTable(this.cfg.table, opt)
|
|
84
74
|
}
|
|
85
75
|
|
|
86
|
-
create(input: Partial<V> = {}): V {
|
|
87
|
-
return {
|
|
88
|
-
...this.cfg.hooks.beforeCreate?.(input),
|
|
89
|
-
} as V
|
|
90
|
-
}
|
|
91
|
-
|
|
92
76
|
async getById(id?: K): Promise<V | null> {
|
|
93
77
|
if (!id) return null
|
|
94
78
|
const [r] = await this.getByIds([id])
|
|
@@ -129,70 +113,38 @@ export class CommonKeyValueDao<V, K extends string = string> {
|
|
|
129
113
|
return r[1]
|
|
130
114
|
}
|
|
131
115
|
|
|
132
|
-
async getByIdOrEmpty(id: K, part: Partial<V> = {}): Promise<V> {
|
|
133
|
-
const [r] = await this.getByIds([id])
|
|
134
|
-
if (r) return r[1]
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
...this.cfg.hooks.beforeCreate?.({}),
|
|
138
|
-
...part,
|
|
139
|
-
} as V
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async patch(id: K, patch: Partial<V>, opt?: CommonKeyValueDaoSaveOptions): Promise<V> {
|
|
143
|
-
const v: V = {
|
|
144
|
-
...(await this.getByIdOrEmpty(id)),
|
|
145
|
-
...patch,
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
await this.save(id, v, opt)
|
|
149
|
-
|
|
150
|
-
return v
|
|
151
|
-
}
|
|
152
|
-
|
|
153
116
|
async getByIds(ids: K[]): Promise<KeyValueTuple<string, V>[]> {
|
|
154
117
|
const entries = await this.cfg.db.getByIds(this.cfg.table, ids)
|
|
155
|
-
if (!this.cfg.
|
|
118
|
+
if (!this.cfg.transformer) return entries as any
|
|
156
119
|
|
|
157
|
-
return await pMap(entries, async ([id,
|
|
120
|
+
return await pMap(entries, async ([id, raw]) => [
|
|
158
121
|
id,
|
|
159
|
-
await this.cfg.
|
|
122
|
+
await this.cfg.transformer!.bufferToValue(raw),
|
|
160
123
|
])
|
|
161
124
|
}
|
|
162
125
|
|
|
163
|
-
async getByIdsAsBuffer(ids: K[]): Promise<
|
|
164
|
-
return
|
|
126
|
+
async getByIdsAsBuffer(ids: K[]): Promise<KeyValueDBTuple[]> {
|
|
127
|
+
return await this.cfg.db.getByIds(this.cfg.table, ids)
|
|
165
128
|
}
|
|
166
129
|
|
|
167
130
|
async save(id: K, value: V, opt?: CommonKeyValueDaoSaveOptions): Promise<void> {
|
|
168
131
|
await this.saveBatch([[id, value]], opt)
|
|
169
132
|
}
|
|
170
133
|
|
|
171
|
-
async saveAsBuffer(id: K, value: Buffer, opt?: CommonKeyValueDaoSaveOptions): Promise<void> {
|
|
172
|
-
await this.cfg.db.saveBatch(this.cfg.table, [[id, value]], opt)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
134
|
async saveBatch(
|
|
176
135
|
entries: KeyValueTuple<K, V>[],
|
|
177
136
|
opt?: CommonKeyValueDaoSaveOptions,
|
|
178
137
|
): Promise<void> {
|
|
179
|
-
const {
|
|
180
|
-
let
|
|
138
|
+
const { transformer } = this.cfg
|
|
139
|
+
let rawEntries: KeyValueDBTuple[]
|
|
181
140
|
|
|
182
|
-
if (!
|
|
183
|
-
|
|
141
|
+
if (!transformer) {
|
|
142
|
+
rawEntries = entries as any
|
|
184
143
|
} else {
|
|
185
|
-
|
|
144
|
+
rawEntries = await pMap(entries, async ([id, v]) => [id, await transformer.valueToBuffer(v)])
|
|
186
145
|
}
|
|
187
146
|
|
|
188
|
-
await this.cfg.db.saveBatch(this.cfg.table,
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async saveBatchAsBuffer(
|
|
192
|
-
entries: KeyValueDBTuple[],
|
|
193
|
-
opt?: CommonKeyValueDaoSaveOptions,
|
|
194
|
-
): Promise<void> {
|
|
195
|
-
await this.cfg.db.saveBatch(this.cfg.table, entries, opt)
|
|
147
|
+
await this.cfg.db.saveBatch(this.cfg.table, rawEntries, opt)
|
|
196
148
|
}
|
|
197
149
|
|
|
198
150
|
async deleteByIds(ids: K[]): Promise<void> {
|
|
@@ -208,16 +160,16 @@ export class CommonKeyValueDao<V, K extends string = string> {
|
|
|
208
160
|
}
|
|
209
161
|
|
|
210
162
|
streamValues(limit?: number): ReadableTyped<V> {
|
|
211
|
-
const {
|
|
163
|
+
const { transformer } = this.cfg
|
|
212
164
|
|
|
213
|
-
if (!
|
|
165
|
+
if (!transformer) {
|
|
214
166
|
return this.cfg.db.streamValues(this.cfg.table, limit) as ReadableTyped<V>
|
|
215
167
|
}
|
|
216
168
|
|
|
217
169
|
return this.cfg.db.streamValues(this.cfg.table, limit).flatMap(
|
|
218
170
|
async buf => {
|
|
219
171
|
try {
|
|
220
|
-
return [await
|
|
172
|
+
return [await transformer.bufferToValue(buf)]
|
|
221
173
|
} catch (err) {
|
|
222
174
|
this.cfg.logger.error(err)
|
|
223
175
|
return [] // SKIP
|
|
@@ -230,18 +182,16 @@ export class CommonKeyValueDao<V, K extends string = string> {
|
|
|
230
182
|
}
|
|
231
183
|
|
|
232
184
|
streamEntries(limit?: number): ReadableTyped<KeyValueTuple<K, V>> {
|
|
233
|
-
const {
|
|
185
|
+
const { transformer } = this.cfg
|
|
234
186
|
|
|
235
|
-
if (!
|
|
187
|
+
if (!transformer) {
|
|
236
188
|
return this.cfg.db.streamEntries(this.cfg.table, limit) as ReadableTyped<KeyValueTuple<K, V>>
|
|
237
189
|
}
|
|
238
190
|
|
|
239
|
-
return (
|
|
240
|
-
this.cfg.db.streamEntries(this.cfg.table, limit) as ReadableTyped<KeyValueTuple<K, Buffer>>
|
|
241
|
-
).flatMap(
|
|
191
|
+
return this.cfg.db.streamEntries(this.cfg.table, limit).flatMap(
|
|
242
192
|
async ([id, buf]) => {
|
|
243
193
|
try {
|
|
244
|
-
return [[id, await
|
|
194
|
+
return [[id as K, await transformer.bufferToValue(buf)]]
|
|
245
195
|
} catch (err) {
|
|
246
196
|
this.cfg.logger.error(err)
|
|
247
197
|
return [] // SKIP
|
|
@@ -272,6 +222,11 @@ export class CommonKeyValueDao<V, K extends string = string> {
|
|
|
272
222
|
* Returns the new value of the field.
|
|
273
223
|
*/
|
|
274
224
|
async increment(id: K, by = 1): Promise<number> {
|
|
275
|
-
|
|
225
|
+
const [t] = await this.cfg.db.incrementBatch(this.cfg.table, [[id, by]])
|
|
226
|
+
return t![1]
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async incrementBatch(entries: IncrementTuple[]): Promise<IncrementTuple[]> {
|
|
230
|
+
return await this.cfg.db.incrementBatch(this.cfg.table, entries)
|
|
276
231
|
}
|
|
277
232
|
}
|
|
@@ -2,7 +2,7 @@ import { AsyncMemoCache, localTime, MISS, NumberOfSeconds } from '@naturalcycles
|
|
|
2
2
|
import { CommonKeyValueDao } from './commonKeyValueDao'
|
|
3
3
|
|
|
4
4
|
export interface CommonKeyValueDaoMemoCacheCfg<VALUE> {
|
|
5
|
-
dao: CommonKeyValueDao<VALUE>
|
|
5
|
+
dao: CommonKeyValueDao<string, VALUE>
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* If set, every `set()` will set `expireAt` (TTL) option.
|
|
@@ -18,7 +18,7 @@ export interface CommonKeyValueDaoMemoCacheCfg<VALUE> {
|
|
|
18
18
|
* Also, does not support .clear(), as it's more dangerous than useful to actually
|
|
19
19
|
* clear the whole table/cache.
|
|
20
20
|
*/
|
|
21
|
-
export class CommonKeyValueDaoMemoCache<VALUE
|
|
21
|
+
export class CommonKeyValueDaoMemoCache<VALUE> implements AsyncMemoCache<string, VALUE> {
|
|
22
22
|
constructor(private cfg: CommonKeyValueDaoMemoCacheCfg<VALUE>) {}
|
|
23
23
|
|
|
24
24
|
async get(k: string): Promise<VALUE | typeof MISS> {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { _range, _sortBy } from '@naturalcycles/js-lib'
|
|
2
|
-
import { CommonKeyValueDB
|
|
1
|
+
import { _range, _sortBy, KeyValueTuple } from '@naturalcycles/js-lib'
|
|
2
|
+
import { CommonKeyValueDB } from '../kv/commonKeyValueDB'
|
|
3
3
|
import { TEST_TABLE } from './test.model'
|
|
4
4
|
|
|
5
5
|
const testIds = _range(1, 4).map(n => `id${n}`)
|
|
6
6
|
|
|
7
|
-
const testEntries:
|
|
7
|
+
const testEntries: KeyValueTuple<string, Buffer>[] = testIds.map(id => [
|
|
8
|
+
id,
|
|
9
|
+
Buffer.from(`${id}value`),
|
|
10
|
+
])
|
|
8
11
|
|
|
9
12
|
export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
|
|
10
13
|
beforeAll(async () => {
|
|
@@ -109,26 +112,26 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
|
|
|
109
112
|
const id2 = 'nonExistingField2'
|
|
110
113
|
|
|
111
114
|
test('increment on a non-existing field should set the value to 1', async () => {
|
|
112
|
-
const result = await db.
|
|
113
|
-
expect(result).
|
|
115
|
+
const result = await db.incrementBatch(TEST_TABLE, [[id, 1]])
|
|
116
|
+
expect(result).toEqual([[id, 1]])
|
|
114
117
|
})
|
|
115
118
|
|
|
116
119
|
test('increment on a existing field should increase the value by one', async () => {
|
|
117
|
-
const result = await db.
|
|
118
|
-
expect(result).
|
|
120
|
+
const result = await db.incrementBatch(TEST_TABLE, [[id, 1]])
|
|
121
|
+
expect(result).toEqual([[id, 2]])
|
|
119
122
|
})
|
|
120
123
|
|
|
121
124
|
test('increment should increase the value by the specified amount', async () => {
|
|
122
|
-
const result = await db.
|
|
123
|
-
expect(result).
|
|
125
|
+
const result = await db.incrementBatch(TEST_TABLE, [[id, 2]])
|
|
126
|
+
expect(result).toEqual([[id, 4]])
|
|
124
127
|
})
|
|
125
128
|
|
|
126
129
|
test('increment 2 ids at the same time', async () => {
|
|
127
|
-
const result = await db.incrementBatch(TEST_TABLE,
|
|
128
|
-
[id
|
|
129
|
-
[id2
|
|
130
|
-
|
|
131
|
-
expect(result).toEqual({
|
|
130
|
+
const result = await db.incrementBatch(TEST_TABLE, [
|
|
131
|
+
[id, 1],
|
|
132
|
+
[id2, 2],
|
|
133
|
+
])
|
|
134
|
+
expect(Object.fromEntries(result)).toEqual({
|
|
132
135
|
[id]: 5,
|
|
133
136
|
[id2]: 2,
|
|
134
137
|
})
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _sortBy, KeyValueTuple } from '@naturalcycles/js-lib'
|
|
2
2
|
import { CommonKeyValueDao } from '../kv/commonKeyValueDao'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
3
|
+
import { CommonKeyValueDB } from '../kv/commonKeyValueDB'
|
|
4
|
+
import { createTestItemsBM, TEST_TABLE, TestItemBM } from './test.model'
|
|
5
|
+
|
|
6
|
+
const testItems = createTestItemsBM(4)
|
|
7
|
+
const testIds = testItems.map(e => e.id)
|
|
8
|
+
const testEntries: KeyValueTuple<string, TestItemBM>[] = testItems.map(e => [e.id, e])
|
|
9
|
+
|
|
10
|
+
export function runCommonKeyValueDaoTest(db: CommonKeyValueDB): void {
|
|
11
|
+
const dao = new CommonKeyValueDao<string, TestItemBM>({
|
|
12
|
+
db,
|
|
13
|
+
table: TEST_TABLE,
|
|
14
|
+
// todo: make this test support "deflatedJson" transformer
|
|
15
|
+
})
|
|
16
|
+
const { support } = db
|
|
7
17
|
|
|
8
|
-
export function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void {
|
|
9
18
|
beforeAll(async () => {
|
|
10
19
|
// Tests in this suite are not isolated,
|
|
11
20
|
// and failing tests can leave the DB in an unexpected state for other tests,
|
|
@@ -20,8 +29,6 @@ export function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void {
|
|
|
20
29
|
await dao.deleteByIds(ids)
|
|
21
30
|
})
|
|
22
31
|
|
|
23
|
-
const { support } = dao.cfg.db
|
|
24
|
-
|
|
25
32
|
test('ping', async () => {
|
|
26
33
|
await dao.ping()
|
|
27
34
|
})
|
|
@@ -43,8 +50,12 @@ export function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void {
|
|
|
43
50
|
await dao.saveBatch(testEntries)
|
|
44
51
|
|
|
45
52
|
const entries = await dao.getByIds(testIds)
|
|
53
|
+
// console.log(typeof entries[0]![1], entries[0]![1])
|
|
54
|
+
|
|
46
55
|
_sortBy(entries, e => e[0], true)
|
|
47
|
-
expect(entries).toEqual(testEntries)
|
|
56
|
+
expect(entries).toEqual(testEntries) // Jest doesn't allow to compare Buffers directly
|
|
57
|
+
// expect(entries.map(e => e[0])).toEqual(testEntries.map(e => e[0]))
|
|
58
|
+
// expect(entries.map(e => e[1].toString())).toEqual(testEntries.map(e => e[1].toString()))
|
|
48
59
|
})
|
|
49
60
|
|
|
50
61
|
test('streamIds', async () => {
|
|
@@ -94,19 +105,33 @@ export function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void {
|
|
|
94
105
|
})
|
|
95
106
|
|
|
96
107
|
if (support.increment) {
|
|
108
|
+
const id = 'nonExistingField'
|
|
109
|
+
const id2 = 'nonExistingField2'
|
|
110
|
+
|
|
97
111
|
test('increment on a non-existing field should set the value to 1', async () => {
|
|
98
|
-
const result = await dao.increment(
|
|
112
|
+
const result = await dao.increment(id)
|
|
99
113
|
expect(result).toBe(1)
|
|
100
114
|
})
|
|
101
115
|
|
|
102
116
|
test('increment on a existing field should increase the value by one', async () => {
|
|
103
|
-
const result = await dao.increment(
|
|
117
|
+
const result = await dao.increment(id)
|
|
104
118
|
expect(result).toBe(2)
|
|
105
119
|
})
|
|
106
120
|
|
|
107
121
|
test('increment should increase the value by the specified amount', async () => {
|
|
108
|
-
const result = await dao.increment(
|
|
122
|
+
const result = await dao.increment(id, 2)
|
|
109
123
|
expect(result).toBe(4)
|
|
110
124
|
})
|
|
125
|
+
|
|
126
|
+
test('increment 2 ids at the same time', async () => {
|
|
127
|
+
const result = await dao.incrementBatch([
|
|
128
|
+
[id, 1],
|
|
129
|
+
[id2, 2],
|
|
130
|
+
])
|
|
131
|
+
expect(Object.fromEntries(result)).toEqual({
|
|
132
|
+
[id]: 5,
|
|
133
|
+
[id2]: 2,
|
|
134
|
+
})
|
|
135
|
+
})
|
|
111
136
|
}
|
|
112
137
|
}
|