@naturalcycles/db-lib 9.22.0 → 9.22.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.
@@ -1,7 +1,7 @@
1
- import { StringMap } from '@naturalcycles/js-lib';
1
+ import { KeyValueTuple, 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 } from '../../kv/commonKeyValueDB';
5
5
  export interface InMemoryKeyValueDBCfg {
6
6
  }
7
7
  export declare class InMemoryKeyValueDB implements CommonKeyValueDB {
@@ -11,16 +11,15 @@ export declare class InMemoryKeyValueDB implements CommonKeyValueDB {
11
11
  count?: boolean;
12
12
  increment?: boolean;
13
13
  };
14
- data: StringMap<StringMap<Buffer>>;
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>;
18
- getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
19
- saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void>;
18
+ getByIds<V>(table: string, ids: string[]): Promise<KeyValueTuple<string, V>[]>;
19
+ saveBatch<V>(table: string, entries: KeyValueTuple<string, V>[]): Promise<void>;
20
20
  streamIds(table: string, limit?: number): ReadableTyped<string>;
21
- streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
22
- streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
21
+ streamValues<V>(table: string, limit?: number): ReadableTyped<V>;
22
+ streamEntries<V>(table: string, limit?: number): ReadableTyped<KeyValueTuple<string, V>>;
23
23
  count(table: string): Promise<number>;
24
- increment(table: string, id: string, by?: number): Promise<number>;
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] > Buffer
12
+ // data[table][id] => V
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, buf]) => (this.data[table][id] = buf));
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 increment(table, id, by = 1) {
42
+ async incrementBatch(table, entries) {
45
43
  this.data[table] ||= {};
46
- const currentValue = this.data[table][id] ? parseInt(this.data[table][id].toString()) : 0;
47
- const newValue = currentValue + by;
48
- this.data[table][id] = Buffer.from(String(newValue));
49
- return newValue;
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 { StringMap, UnixTimestampNumber } from '@naturalcycles/js-lib';
1
+ import { Integer, KeyValueTuple, UnixTimestampNumber } from '@naturalcycles/js-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
3
  import { CommonDBCreateOptions } from '../db.model';
4
4
  /**
@@ -25,37 +25,31 @@ export interface CommonKeyValueDB {
25
25
  *
26
26
  * Currently it is NOT required to maintain the same order as input `ids`.
27
27
  */
28
- getByIds: (table: string, ids: string[]) => Promise<KeyValueDBTuple[]>;
28
+ getByIds: <V>(table: string, ids: string[]) => Promise<KeyValueTuple<string, V>[]>;
29
29
  deleteByIds: (table: string, ids: string[]) => Promise<void>;
30
- saveBatch: (table: string, entries: KeyValueDBTuple[], opt?: CommonKeyValueDBSaveBatchOptions) => Promise<void>;
30
+ saveBatch: <V>(table: string, entries: KeyValueTuple<string, V>[], opt?: CommonKeyValueDBSaveBatchOptions) => Promise<void>;
31
31
  streamIds: (table: string, limit?: number) => ReadableTyped<string>;
32
- streamValues: (table: string, limit?: number) => ReadableTyped<Buffer>;
33
- streamEntries: (table: string, limit?: number) => ReadableTyped<KeyValueDBTuple>;
32
+ streamValues: <V>(table: string, limit?: number) => ReadableTyped<V>;
33
+ streamEntries: <V>(table: string, limit?: number) => ReadableTyped<KeyValueTuple<string, V>>;
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 incrementMap, increment each key of it by the given amount (value of the map).
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
- * { key1: 2, key2: 3 }
40
+ * [
41
+ * ['key1', 2],
42
+ * ['key2', 3],
43
+ * ]
50
44
  * would increment `key1` by 2, and `key2` by 3.
51
45
  *
52
- * Returns the incrementMap with the same keys and updated values.
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, incrementMap: StringMap<number>) => Promise<StringMap<number>>;
50
+ incrementBatch: (table: string, entries: IncrementTuple[]) => Promise<IncrementTuple[]>;
57
51
  }
58
- export type KeyValueDBTuple = [key: string, value: Buffer];
52
+ export type IncrementTuple = [key: string, value: Integer];
59
53
  export interface CommonKeyValueDBSaveBatchOptions {
60
54
  /**
61
55
  * If set (and if it's implemented by the driver) - will set expiry TTL for each key of the batch.
@@ -2,8 +2,8 @@ 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';
6
- export interface CommonKeyValueDaoCfg<V> {
5
+ import { CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, IncrementTuple } from './commonKeyValueDB';
6
+ export interface CommonKeyValueDaoCfg<RAW_V, V = RAW_V> {
7
7
  db: CommonKeyValueDB;
8
8
  table: string;
9
9
  /**
@@ -23,40 +23,31 @@ export interface CommonKeyValueDaoCfg<V> {
23
23
  * @default false
24
24
  */
25
25
  logStarted?: boolean;
26
- hooks?: {
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, RAW_V>;
37
27
  }
38
28
  export type CommonKeyValueDaoSaveOptions = CommonKeyValueDBSaveBatchOptions;
39
- export declare class CommonKeyValueDao<V, K extends string = string> {
40
- constructor(cfg: CommonKeyValueDaoCfg<V>);
41
- cfg: CommonKeyValueDaoCfg<V> & {
42
- hooks: NonNullable<CommonKeyValueDaoCfg<V>['hooks']>;
29
+ export interface CommonKeyValueDaoTransformer<V, RAW_V> {
30
+ valueToRaw: (v: V) => Promise<RAW_V>;
31
+ rawToValue: (raw: RAW_V) => Promise<V>;
32
+ }
33
+ export declare const commonKeyValueDaoDeflatedJsonTransformer: CommonKeyValueDaoTransformer<any, Buffer>;
34
+ export declare class CommonKeyValueDao<K extends string, RAW_V, V = RAW_V> {
35
+ constructor(cfg: CommonKeyValueDaoCfg<RAW_V, V>);
36
+ cfg: CommonKeyValueDaoCfg<RAW_V, V> & {
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
- getByIdAsBuffer(id?: K): Promise<Buffer | null>;
42
+ getByIdRaw(id?: K): Promise<RAW_V | null>;
50
43
  requireById(id: K): Promise<V>;
51
- 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>;
44
+ requireByIdRaw(id: K): Promise<RAW_V>;
54
45
  getByIds(ids: K[]): Promise<KeyValueTuple<string, V>[]>;
55
- getByIdsAsBuffer(ids: K[]): Promise<KeyValueTuple<K, Buffer>[]>;
46
+ getByIdsRaw(ids: K[]): Promise<KeyValueTuple<K, RAW_V>[]>;
56
47
  save(id: K, value: V, opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
57
- saveAsBuffer(id: K, value: Buffer, opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
48
+ saveRaw(id: K, value: RAW_V, opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
58
49
  saveBatch(entries: KeyValueTuple<K, V>[], opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
59
- saveBatchAsBuffer(entries: KeyValueDBTuple[], opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
50
+ saveBatchRaw(entries: KeyValueTuple<K, RAW_V>[], opt?: CommonKeyValueDaoSaveOptions): Promise<void>;
60
51
  deleteByIds(ids: K[]): Promise<void>;
61
52
  deleteById(id: K): Promise<void>;
62
53
  streamIds(limit?: number): ReadableTyped<K>;
@@ -72,4 +63,5 @@ export declare class CommonKeyValueDao<V, K extends string = string> {
72
63
  * Returns the new value of the field.
73
64
  */
74
65
  increment(id: K, by?: number): Promise<number>;
66
+ incrementBatch(entries: IncrementTuple[]): Promise<IncrementTuple[]>;
75
67
  }
@@ -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
+ valueToRaw: async (v) => await (0, nodejs_lib_1.deflateString)(JSON.stringify(v)),
8
+ rawToValue: async (raw) => JSON.parse(await (0, nodejs_lib_1.inflateToString)(raw)),
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,18 +22,13 @@ 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;
37
28
  const [r] = await this.getByIds([id]);
38
29
  return r?.[1] || null;
39
30
  }
40
- async getByIdAsBuffer(id) {
31
+ async getByIdRaw(id) {
41
32
  if (!id)
42
33
  return null;
43
34
  const [r] = await this.cfg.db.getByIds(this.cfg.table, [id]);
@@ -54,7 +45,7 @@ class CommonKeyValueDao {
54
45
  }
55
46
  return r[1];
56
47
  }
57
- async requireByIdAsBuffer(id) {
48
+ async requireByIdRaw(id) {
58
49
  const [r] = await this.cfg.db.getByIds(this.cfg.table, [id]);
59
50
  if (!r) {
60
51
  const { table } = this.cfg;
@@ -65,53 +56,36 @@ 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.hooks.mapBufferToValue)
61
+ if (!this.cfg.transformer)
88
62
  return entries;
89
- return await (0, js_lib_1.pMap)(entries, async ([id, buf]) => [
63
+ return await (0, js_lib_1.pMap)(entries, async ([id, raw]) => [
90
64
  id,
91
- await this.cfg.hooks.mapBufferToValue(buf),
65
+ await this.cfg.transformer.rawToValue(raw),
92
66
  ]);
93
67
  }
94
- async getByIdsAsBuffer(ids) {
68
+ async getByIdsRaw(ids) {
95
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) {
74
+ async saveRaw(id, value, opt) {
101
75
  await this.cfg.db.saveBatch(this.cfg.table, [[id, value]], opt);
102
76
  }
103
77
  async saveBatch(entries, opt) {
104
- const { mapValueToBuffer } = this.cfg.hooks;
105
- let bufferEntries;
106
- if (!mapValueToBuffer) {
107
- bufferEntries = entries;
78
+ const { transformer } = this.cfg;
79
+ let rawEntries;
80
+ if (!transformer) {
81
+ rawEntries = entries;
108
82
  }
109
83
  else {
110
- bufferEntries = await (0, js_lib_1.pMap)(entries, async ([id, v]) => [id, await mapValueToBuffer(v)]);
84
+ rawEntries = await (0, js_lib_1.pMap)(entries, async ([id, v]) => [id, await transformer.valueToRaw(v)]);
111
85
  }
112
- await this.cfg.db.saveBatch(this.cfg.table, bufferEntries, opt);
86
+ await this.cfg.db.saveBatch(this.cfg.table, rawEntries, opt);
113
87
  }
114
- async saveBatchAsBuffer(entries, opt) {
88
+ async saveBatchRaw(entries, opt) {
115
89
  await this.cfg.db.saveBatch(this.cfg.table, entries, opt);
116
90
  }
117
91
  async deleteByIds(ids) {
@@ -124,13 +98,13 @@ class CommonKeyValueDao {
124
98
  return this.cfg.db.streamIds(this.cfg.table, limit);
125
99
  }
126
100
  streamValues(limit) {
127
- const { mapBufferToValue } = this.cfg.hooks;
128
- if (!mapBufferToValue) {
101
+ const { transformer } = this.cfg;
102
+ if (!transformer) {
129
103
  return this.cfg.db.streamValues(this.cfg.table, limit);
130
104
  }
131
- return this.cfg.db.streamValues(this.cfg.table, limit).flatMap(async (buf) => {
105
+ return this.cfg.db.streamValues(this.cfg.table, limit).flatMap(async (raw) => {
132
106
  try {
133
- return [await mapBufferToValue(buf)];
107
+ return [await transformer.rawToValue(raw)];
134
108
  }
135
109
  catch (err) {
136
110
  this.cfg.logger.error(err);
@@ -141,13 +115,13 @@ class CommonKeyValueDao {
141
115
  });
142
116
  }
143
117
  streamEntries(limit) {
144
- const { mapBufferToValue } = this.cfg.hooks;
145
- if (!mapBufferToValue) {
118
+ const { transformer } = this.cfg;
119
+ if (!transformer) {
146
120
  return this.cfg.db.streamEntries(this.cfg.table, limit);
147
121
  }
148
- return this.cfg.db.streamEntries(this.cfg.table, limit).flatMap(async ([id, buf]) => {
122
+ return this.cfg.db.streamEntries(this.cfg.table, limit).flatMap(async ([id, raw]) => {
149
123
  try {
150
- return [[id, await mapBufferToValue(buf)]];
124
+ return [[id, await transformer.rawToValue(raw)]];
151
125
  }
152
126
  catch (err) {
153
127
  this.cfg.logger.error(err);
@@ -173,7 +147,11 @@ class CommonKeyValueDao {
173
147
  * Returns the new value of the field.
174
148
  */
175
149
  async increment(id, by = 1) {
176
- return await this.cfg.db.increment(this.cfg.table, id, by);
150
+ const [t] = await this.cfg.db.incrementBatch(this.cfg.table, [[id, by]]);
151
+ return t[1];
152
+ }
153
+ async incrementBatch(entries) {
154
+ return await this.cfg.db.incrementBatch(this.cfg.table, entries);
177
155
  }
178
156
  }
179
157
  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 = any> implements AsyncMemoCache<string, 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 => [id, Buffer.from(`${id}value`)]);
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.increment(test_model_1.TEST_TABLE, id);
93
- expect(result).toBe(1);
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.increment(test_model_1.TEST_TABLE, id);
97
- expect(result).toBe(2);
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.increment(test_model_1.TEST_TABLE, id, 2);
101
- expect(result).toBe(4);
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]: 1,
106
- [id2]: 2,
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 { CommonKeyValueDao } from '../kv/commonKeyValueDao';
2
- export declare function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void;
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 testIds = (0, js_lib_1._range)(1, 4).map(n => `id${n}`);
6
- const testEntries = testIds.map(id => [id, Buffer.from(`${id}value`)]);
7
- function runCommonKeyValueDaoTest(dao) {
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('nonExistingField');
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('nonExistingField');
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('nonExistingField', 2);
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
@@ -45,7 +45,7 @@
45
45
  "engines": {
46
46
  "node": ">=20.13"
47
47
  },
48
- "version": "9.22.0",
48
+ "version": "9.22.1",
49
49
  "description": "Lowest Common Denominator API to supported Databases",
50
50
  "keywords": [
51
51
  "db",
@@ -1,11 +1,11 @@
1
1
  import { Readable } from 'node:stream'
2
- import { _stringMapEntries, StringMap } from '@naturalcycles/js-lib'
2
+ import { KeyValueTuple, 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
- KeyValueDBTuple,
8
+ IncrementTuple,
9
9
  } from '../../kv/commonKeyValueDB'
10
10
 
11
11
  export interface InMemoryKeyValueDBCfg {}
@@ -17,8 +17,8 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
17
17
  ...commonKeyValueDBFullSupport,
18
18
  }
19
19
 
20
- // data[table][id] > Buffer
21
- data: StringMap<StringMap<Buffer>> = {}
20
+ // data[table][id] => V
21
+ data: StringMap<StringMap<any>> = {}
22
22
 
23
23
  async ping(): Promise<void> {}
24
24
 
@@ -29,26 +29,25 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
29
29
  ids.forEach(id => delete this.data[table]![id])
30
30
  }
31
31
 
32
- // todo: but should we work with Tuples or Objects?
33
- async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
32
+ async getByIds<V>(table: string, ids: string[]): Promise<KeyValueTuple<string, V>[]> {
34
33
  this.data[table] ||= {}
35
- return ids.map(id => [id, this.data[table]![id]!] as KeyValueDBTuple).filter(e => e[1])
34
+ return ids.map(id => [id, this.data[table]![id]!] as KeyValueTuple<string, V>).filter(e => e[1])
36
35
  }
37
36
 
38
- async saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void> {
37
+ async saveBatch<V>(table: string, entries: KeyValueTuple<string, V>[]): Promise<void> {
39
38
  this.data[table] ||= {}
40
- entries.forEach(([id, buf]) => (this.data[table]![id] = buf))
39
+ entries.forEach(([id, v]) => (this.data[table]![id] = v))
41
40
  }
42
41
 
43
42
  streamIds(table: string, limit?: number): ReadableTyped<string> {
44
43
  return Readable.from(Object.keys(this.data[table] || {}).slice(0, limit))
45
44
  }
46
45
 
47
- streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
46
+ streamValues<V>(table: string, limit?: number): ReadableTyped<V> {
48
47
  return Readable.from(Object.values(this.data[table] || {}).slice(0, limit))
49
48
  }
50
49
 
51
- streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
50
+ streamEntries<V>(table: string, limit?: number): ReadableTyped<KeyValueTuple<string, V>> {
52
51
  return Readable.from(Object.entries(this.data[table] || {}).slice(0, limit))
53
52
  }
54
53
 
@@ -57,28 +56,13 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
57
56
  return Object.keys(this.data[table]).length
58
57
  }
59
58
 
60
- async increment(table: string, id: string, by = 1): Promise<number> {
59
+ async incrementBatch(table: string, entries: IncrementTuple[]): Promise<IncrementTuple[]> {
61
60
  this.data[table] ||= {}
62
61
 
63
- const currentValue = this.data[table][id] ? parseInt(this.data[table][id].toString()) : 0
64
- const newValue = currentValue + by
65
- this.data[table][id] = Buffer.from(String(newValue))
66
-
67
- return newValue
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
62
+ return entries.map(([id, by]) => {
63
+ const newValue = ((this.data[table]![id] as number | undefined) || 0) + by
64
+ this.data[table]![id] = newValue
65
+ return [id, newValue]
66
+ })
83
67
  }
84
68
  }
@@ -1,4 +1,4 @@
1
- import { StringMap, UnixTimestampNumber } from '@naturalcycles/js-lib'
1
+ import { Integer, KeyValueTuple, UnixTimestampNumber } from '@naturalcycles/js-lib'
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib'
3
3
  import { CommonDBCreateOptions } from '../db.model'
4
4
 
@@ -29,48 +29,41 @@ export interface CommonKeyValueDB {
29
29
  *
30
30
  * Currently it is NOT required to maintain the same order as input `ids`.
31
31
  */
32
- getByIds: (table: string, ids: string[]) => Promise<KeyValueDBTuple[]>
32
+ getByIds: <V>(table: string, ids: string[]) => Promise<KeyValueTuple<string, V>[]>
33
33
 
34
34
  deleteByIds: (table: string, ids: string[]) => Promise<void>
35
35
 
36
- saveBatch: (
36
+ saveBatch: <V>(
37
37
  table: string,
38
- entries: KeyValueDBTuple[],
38
+ entries: KeyValueTuple<string, V>[],
39
39
  opt?: CommonKeyValueDBSaveBatchOptions,
40
40
  ) => Promise<void>
41
41
 
42
42
  streamIds: (table: string, limit?: number) => ReadableTyped<string>
43
- streamValues: (table: string, limit?: number) => ReadableTyped<Buffer>
44
- streamEntries: (table: string, limit?: number) => ReadableTyped<KeyValueDBTuple>
43
+ streamValues: <V>(table: string, limit?: number) => ReadableTyped<V>
44
+ streamEntries: <V>(table: string, limit?: number) => ReadableTyped<KeyValueTuple<string, V>>
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 incrementMap, increment each key of it by the given amount (value of the map).
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
- * { key1: 2, key2: 3 }
53
+ * [
54
+ * ['key1', 2],
55
+ * ['key2', 3],
56
+ * ]
64
57
  * would increment `key1` by 2, and `key2` by 3.
65
58
  *
66
- * Returns the incrementMap with the same keys and updated values.
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, incrementMap: StringMap<number>) => Promise<StringMap<number>>
63
+ incrementBatch: (table: string, entries: IncrementTuple[]) => Promise<IncrementTuple[]>
71
64
  }
72
65
 
73
- export type KeyValueDBTuple = [key: string, value: Buffer]
66
+ export type IncrementTuple = [key: string, value: Integer]
74
67
 
75
68
  export interface CommonKeyValueDBSaveBatchOptions {
76
69
  /**
@@ -5,10 +5,10 @@ import { CommonDBCreateOptions } from '../db.model'
5
5
  import {
6
6
  CommonKeyValueDB,
7
7
  CommonKeyValueDBSaveBatchOptions,
8
- KeyValueDBTuple,
8
+ IncrementTuple,
9
9
  } from './commonKeyValueDB'
10
10
 
11
- export interface CommonKeyValueDaoCfg<V> {
11
+ export interface CommonKeyValueDaoCfg<RAW_V, V = RAW_V> {
12
12
  db: CommonKeyValueDB
13
13
 
14
14
  table: string
@@ -34,44 +34,33 @@ export interface CommonKeyValueDaoCfg<V> {
34
34
  */
35
35
  logStarted?: boolean
36
36
 
37
- hooks?: {
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
37
+ transformer?: CommonKeyValueDaoTransformer<V, RAW_V>
49
38
  }
50
39
 
51
40
  export type CommonKeyValueDaoSaveOptions = CommonKeyValueDBSaveBatchOptions
52
41
 
42
+ export interface CommonKeyValueDaoTransformer<V, RAW_V> {
43
+ valueToRaw: (v: V) => Promise<RAW_V>
44
+ rawToValue: (raw: RAW_V) => Promise<V>
45
+ }
46
+
47
+ export const commonKeyValueDaoDeflatedJsonTransformer: CommonKeyValueDaoTransformer<any, Buffer> = {
48
+ valueToRaw: async v => await deflateString(JSON.stringify(v)),
49
+ rawToValue: async raw => JSON.parse(await inflateToString(raw)),
50
+ }
51
+
53
52
  // todo: logging
54
53
  // todo: readonly
55
54
 
56
- export class CommonKeyValueDao<V, K extends string = string> {
57
- constructor(cfg: CommonKeyValueDaoCfg<V>) {
55
+ export class CommonKeyValueDao<K extends string, RAW_V, V = RAW_V> {
56
+ constructor(cfg: CommonKeyValueDaoCfg<RAW_V, V>) {
58
57
  this.cfg = {
59
- hooks: {},
60
58
  logger: console,
61
59
  ...cfg,
62
60
  }
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
61
  }
72
62
 
73
- cfg: CommonKeyValueDaoCfg<V> & {
74
- hooks: NonNullable<CommonKeyValueDaoCfg<V>['hooks']>
63
+ cfg: CommonKeyValueDaoCfg<RAW_V, V> & {
75
64
  logger: CommonLogger
76
65
  }
77
66
 
@@ -83,21 +72,15 @@ export class CommonKeyValueDao<V, K extends string = string> {
83
72
  await this.cfg.db.createTable(this.cfg.table, opt)
84
73
  }
85
74
 
86
- create(input: Partial<V> = {}): V {
87
- return {
88
- ...this.cfg.hooks.beforeCreate?.(input),
89
- } as V
90
- }
91
-
92
75
  async getById(id?: K): Promise<V | null> {
93
76
  if (!id) return null
94
77
  const [r] = await this.getByIds([id])
95
78
  return r?.[1] || null
96
79
  }
97
80
 
98
- async getByIdAsBuffer(id?: K): Promise<Buffer | null> {
81
+ async getByIdRaw(id?: K): Promise<RAW_V | null> {
99
82
  if (!id) return null
100
- const [r] = await this.cfg.db.getByIds(this.cfg.table, [id])
83
+ const [r] = await this.cfg.db.getByIds<RAW_V>(this.cfg.table, [id])
101
84
  return r?.[1] || null
102
85
  }
103
86
 
@@ -115,8 +98,8 @@ export class CommonKeyValueDao<V, K extends string = string> {
115
98
  return r[1]
116
99
  }
117
100
 
118
- async requireByIdAsBuffer(id: K): Promise<Buffer> {
119
- const [r] = await this.cfg.db.getByIds(this.cfg.table, [id])
101
+ async requireByIdRaw(id: K): Promise<RAW_V> {
102
+ const [r] = await this.cfg.db.getByIds<RAW_V>(this.cfg.table, [id])
120
103
 
121
104
  if (!r) {
122
105
  const { table } = this.cfg
@@ -129,46 +112,25 @@ export class CommonKeyValueDao<V, K extends string = string> {
129
112
  return r[1]
130
113
  }
131
114
 
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
115
  async getByIds(ids: K[]): Promise<KeyValueTuple<string, V>[]> {
154
- const entries = await this.cfg.db.getByIds(this.cfg.table, ids)
155
- if (!this.cfg.hooks.mapBufferToValue) return entries as any
116
+ const entries = await this.cfg.db.getByIds<RAW_V>(this.cfg.table, ids)
117
+ if (!this.cfg.transformer) return entries as any
156
118
 
157
- return await pMap(entries, async ([id, buf]) => [
119
+ return await pMap(entries, async ([id, raw]) => [
158
120
  id,
159
- await this.cfg.hooks.mapBufferToValue!(buf),
121
+ await this.cfg.transformer!.rawToValue(raw),
160
122
  ])
161
123
  }
162
124
 
163
- async getByIdsAsBuffer(ids: K[]): Promise<KeyValueTuple<K, Buffer>[]> {
164
- return (await this.cfg.db.getByIds(this.cfg.table, ids)) as KeyValueTuple<K, Buffer>[]
125
+ async getByIdsRaw(ids: K[]): Promise<KeyValueTuple<K, RAW_V>[]> {
126
+ return (await this.cfg.db.getByIds(this.cfg.table, ids)) as KeyValueTuple<K, RAW_V>[]
165
127
  }
166
128
 
167
129
  async save(id: K, value: V, opt?: CommonKeyValueDaoSaveOptions): Promise<void> {
168
130
  await this.saveBatch([[id, value]], opt)
169
131
  }
170
132
 
171
- async saveAsBuffer(id: K, value: Buffer, opt?: CommonKeyValueDaoSaveOptions): Promise<void> {
133
+ async saveRaw(id: K, value: RAW_V, opt?: CommonKeyValueDaoSaveOptions): Promise<void> {
172
134
  await this.cfg.db.saveBatch(this.cfg.table, [[id, value]], opt)
173
135
  }
174
136
 
@@ -176,20 +138,20 @@ export class CommonKeyValueDao<V, K extends string = string> {
176
138
  entries: KeyValueTuple<K, V>[],
177
139
  opt?: CommonKeyValueDaoSaveOptions,
178
140
  ): Promise<void> {
179
- const { mapValueToBuffer } = this.cfg.hooks
180
- let bufferEntries: KeyValueDBTuple[]
141
+ const { transformer } = this.cfg
142
+ let rawEntries: KeyValueTuple<string, RAW_V>[]
181
143
 
182
- if (!mapValueToBuffer) {
183
- bufferEntries = entries as any
144
+ if (!transformer) {
145
+ rawEntries = entries as any
184
146
  } else {
185
- bufferEntries = await pMap(entries, async ([id, v]) => [id, await mapValueToBuffer(v)])
147
+ rawEntries = await pMap(entries, async ([id, v]) => [id, await transformer.valueToRaw(v)])
186
148
  }
187
149
 
188
- await this.cfg.db.saveBatch(this.cfg.table, bufferEntries, opt)
150
+ await this.cfg.db.saveBatch(this.cfg.table, rawEntries, opt)
189
151
  }
190
152
 
191
- async saveBatchAsBuffer(
192
- entries: KeyValueDBTuple[],
153
+ async saveBatchRaw(
154
+ entries: KeyValueTuple<K, RAW_V>[],
193
155
  opt?: CommonKeyValueDaoSaveOptions,
194
156
  ): Promise<void> {
195
157
  await this.cfg.db.saveBatch(this.cfg.table, entries, opt)
@@ -208,16 +170,16 @@ export class CommonKeyValueDao<V, K extends string = string> {
208
170
  }
209
171
 
210
172
  streamValues(limit?: number): ReadableTyped<V> {
211
- const { mapBufferToValue } = this.cfg.hooks
173
+ const { transformer } = this.cfg
212
174
 
213
- if (!mapBufferToValue) {
214
- return this.cfg.db.streamValues(this.cfg.table, limit) as ReadableTyped<V>
175
+ if (!transformer) {
176
+ return this.cfg.db.streamValues<V>(this.cfg.table, limit)
215
177
  }
216
178
 
217
- return this.cfg.db.streamValues(this.cfg.table, limit).flatMap(
218
- async buf => {
179
+ return this.cfg.db.streamValues<RAW_V>(this.cfg.table, limit).flatMap(
180
+ async raw => {
219
181
  try {
220
- return [await mapBufferToValue(buf)]
182
+ return [await transformer.rawToValue(raw)]
221
183
  } catch (err) {
222
184
  this.cfg.logger.error(err)
223
185
  return [] // SKIP
@@ -230,18 +192,18 @@ export class CommonKeyValueDao<V, K extends string = string> {
230
192
  }
231
193
 
232
194
  streamEntries(limit?: number): ReadableTyped<KeyValueTuple<K, V>> {
233
- const { mapBufferToValue } = this.cfg.hooks
195
+ const { transformer } = this.cfg
234
196
 
235
- if (!mapBufferToValue) {
236
- return this.cfg.db.streamEntries(this.cfg.table, limit) as ReadableTyped<KeyValueTuple<K, V>>
197
+ if (!transformer) {
198
+ return this.cfg.db.streamEntries<V>(this.cfg.table, limit) as any
237
199
  }
238
200
 
239
201
  return (
240
- this.cfg.db.streamEntries(this.cfg.table, limit) as ReadableTyped<KeyValueTuple<K, Buffer>>
202
+ this.cfg.db.streamEntries(this.cfg.table, limit) as ReadableTyped<KeyValueTuple<K, RAW_V>>
241
203
  ).flatMap(
242
- async ([id, buf]) => {
204
+ async ([id, raw]) => {
243
205
  try {
244
- return [[id, await mapBufferToValue(buf)]]
206
+ return [[id, await transformer.rawToValue(raw)]]
245
207
  } catch (err) {
246
208
  this.cfg.logger.error(err)
247
209
  return [] // SKIP
@@ -272,6 +234,11 @@ export class CommonKeyValueDao<V, K extends string = string> {
272
234
  * Returns the new value of the field.
273
235
  */
274
236
  async increment(id: K, by = 1): Promise<number> {
275
- return await this.cfg.db.increment(this.cfg.table, id, by)
237
+ const [t] = await this.cfg.db.incrementBatch(this.cfg.table, [[id, by]])
238
+ return t![1]
239
+ }
240
+
241
+ async incrementBatch(entries: IncrementTuple[]): Promise<IncrementTuple[]> {
242
+ return await this.cfg.db.incrementBatch(this.cfg.table, entries)
276
243
  }
277
244
  }
@@ -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 = any> implements AsyncMemoCache<string, 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, KeyValueDBTuple } from '../kv/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: KeyValueDBTuple[] = testIds.map(id => [id, Buffer.from(`${id}value`)])
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 () => {
@@ -47,7 +50,7 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
47
50
  test('saveBatch, then getByIds', async () => {
48
51
  await db.saveBatch(TEST_TABLE, testEntries)
49
52
 
50
- const entries = await db.getByIds(TEST_TABLE, testIds)
53
+ const entries = await db.getByIds<Buffer>(TEST_TABLE, testIds)
51
54
  _sortBy(entries, e => e[0], true)
52
55
  expect(entries).toEqual(testEntries)
53
56
  })
@@ -73,26 +76,26 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
73
76
  })
74
77
 
75
78
  test('streamValues', async () => {
76
- const values = await db.streamValues(TEST_TABLE).toArray()
79
+ const values = await db.streamValues<Buffer>(TEST_TABLE).toArray()
77
80
  values.sort()
78
81
  expect(values).toEqual(testEntries.map(e => e[1]))
79
82
  })
80
83
 
81
84
  test('streamValues limited', async () => {
82
- const valuesLimited = await db.streamValues(TEST_TABLE, 2).toArray()
85
+ const valuesLimited = await db.streamValues<Buffer>(TEST_TABLE, 2).toArray()
83
86
  // valuesLimited.sort()
84
87
  // expect(valuesLimited).toEqual(testEntries.map(e => e[1]).slice(0, 2))
85
88
  expect(valuesLimited.length).toBe(2)
86
89
  })
87
90
 
88
91
  test('streamEntries', async () => {
89
- const entries = await db.streamEntries(TEST_TABLE).toArray()
92
+ const entries = await db.streamEntries<Buffer>(TEST_TABLE).toArray()
90
93
  entries.sort()
91
94
  expect(entries).toEqual(testEntries)
92
95
  })
93
96
 
94
97
  test('streamEntries limited', async () => {
95
- const entriesLimited = await db.streamEntries(TEST_TABLE, 2).toArray()
98
+ const entriesLimited = await db.streamEntries<Buffer>(TEST_TABLE, 2).toArray()
96
99
  // entriesLimited.sort()
97
100
  // expect(entriesLimited).toEqual(testEntries.slice(0, 2))
98
101
  expect(entriesLimited.length).toBe(2)
@@ -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.increment(TEST_TABLE, id)
113
- expect(result).toBe(1)
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.increment(TEST_TABLE, id)
118
- expect(result).toBe(2)
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.increment(TEST_TABLE, id, 2)
123
- expect(result).toBe(4)
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]: 1,
129
- [id2]: 2,
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 { _range, _sortBy } from '@naturalcycles/js-lib'
1
+ import { _sortBy, KeyValueTuple } from '@naturalcycles/js-lib'
2
2
  import { CommonKeyValueDao } from '../kv/commonKeyValueDao'
3
- import { KeyValueDBTuple } from '../kv/commonKeyValueDB'
4
-
5
- const testIds = _range(1, 4).map(n => `id${n}`)
6
- const testEntries: KeyValueDBTuple[] = testIds.map(id => [id, Buffer.from(`${id}value`)])
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('nonExistingField')
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('nonExistingField')
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('nonExistingField', 2)
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
  }