@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.
@@ -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<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>;
@@ -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
- 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] => 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, 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, 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 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
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
- 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>;
37
27
  }
38
28
  export type CommonKeyValueDaoSaveOptions = CommonKeyValueDBSaveBatchOptions;
39
- export declare class CommonKeyValueDao<V, K extends string = string> {
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<KeyValueTuple<K, Buffer>[]>;
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.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.bufferToValue(raw),
92
66
  ]);
93
67
  }
94
68
  async getByIdsAsBuffer(ids) {
95
- return (await this.cfg.db.getByIds(this.cfg.table, ids));
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 { mapValueToBuffer } = this.cfg.hooks;
105
- let bufferEntries;
106
- if (!mapValueToBuffer) {
107
- bufferEntries = entries;
75
+ const { transformer } = this.cfg;
76
+ let rawEntries;
77
+ if (!transformer) {
78
+ rawEntries = entries;
108
79
  }
109
80
  else {
110
- bufferEntries = await (0, js_lib_1.pMap)(entries, async ([id, v]) => [id, await mapValueToBuffer(v)]);
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, bufferEntries, opt);
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 { mapBufferToValue } = this.cfg.hooks;
128
- if (!mapBufferToValue) {
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 mapBufferToValue(buf)];
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 { mapBufferToValue } = this.cfg.hooks;
145
- if (!mapBufferToValue) {
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 mapBufferToValue(buf)]];
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
- return await this.cfg.db.increment(this.cfg.table, id, by);
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 = 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.23.0",
49
49
  "description": "Lowest Common Denominator API to supported Databases",
50
50
  "keywords": [
51
51
  "db",
@@ -1,10 +1,11 @@
1
1
  import { Readable } from 'node:stream'
2
- import { _stringMapEntries, StringMap } from '@naturalcycles/js-lib'
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] > Buffer
21
- data: StringMap<StringMap<Buffer>> = {}
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, buf]) => (this.data[table]![id] = buf))
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 increment(table: string, id: string, by = 1): Promise<number> {
60
+ async incrementBatch(table: string, entries: IncrementTuple[]): Promise<IncrementTuple[]> {
61
61
  this.data[table] ||= {}
62
62
 
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
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 { StringMap, UnixTimestampNumber } from '@naturalcycles/js-lib'
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 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
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
- 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
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<V, K extends string = string> {
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.hooks.mapBufferToValue) return entries as any
118
+ if (!this.cfg.transformer) return entries as any
156
119
 
157
- return await pMap(entries, async ([id, buf]) => [
120
+ return await pMap(entries, async ([id, raw]) => [
158
121
  id,
159
- await this.cfg.hooks.mapBufferToValue!(buf),
122
+ await this.cfg.transformer!.bufferToValue(raw),
160
123
  ])
161
124
  }
162
125
 
163
- async getByIdsAsBuffer(ids: K[]): Promise<KeyValueTuple<K, Buffer>[]> {
164
- return (await this.cfg.db.getByIds(this.cfg.table, ids)) as KeyValueTuple<K, Buffer>[]
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 { mapValueToBuffer } = this.cfg.hooks
180
- let bufferEntries: KeyValueDBTuple[]
138
+ const { transformer } = this.cfg
139
+ let rawEntries: KeyValueDBTuple[]
181
140
 
182
- if (!mapValueToBuffer) {
183
- bufferEntries = entries as any
141
+ if (!transformer) {
142
+ rawEntries = entries as any
184
143
  } else {
185
- bufferEntries = await pMap(entries, async ([id, v]) => [id, await mapValueToBuffer(v)])
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, bufferEntries, opt)
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 { mapBufferToValue } = this.cfg.hooks
163
+ const { transformer } = this.cfg
212
164
 
213
- if (!mapBufferToValue) {
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 mapBufferToValue(buf)]
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 { mapBufferToValue } = this.cfg.hooks
185
+ const { transformer } = this.cfg
234
186
 
235
- if (!mapBufferToValue) {
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 mapBufferToValue(buf)]]
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
- return await this.cfg.db.increment(this.cfg.table, id, by)
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 = 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 () => {
@@ -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
  }