@naturalcycles/db-lib 9.21.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.
Files changed (43) hide show
  1. package/dist/adapter/cachedb/cache.db.d.ts +2 -2
  2. package/dist/adapter/cachedb/cache.db.js +4 -3
  3. package/dist/adapter/file/file.db.js +2 -1
  4. package/dist/adapter/inmemory/inMemory.db.d.ts +7 -6
  5. package/dist/adapter/inmemory/inMemory.db.js +15 -13
  6. package/dist/adapter/inmemory/inMemoryKeyValueDB.d.ts +8 -8
  7. package/dist/adapter/inmemory/inMemoryKeyValueDB.js +8 -7
  8. package/dist/base.common.db.d.ts +4 -3
  9. package/dist/base.common.db.js +5 -2
  10. package/dist/common.db.d.ts +23 -14
  11. package/dist/common.db.js +2 -2
  12. package/dist/commondao/common.dao.d.ts +16 -5
  13. package/dist/commondao/common.dao.js +37 -8
  14. package/dist/db.model.d.ts +0 -13
  15. package/dist/db.model.js +1 -17
  16. package/dist/kv/commonKeyValueDB.d.ts +18 -10
  17. package/dist/kv/commonKeyValueDao.d.ts +17 -25
  18. package/dist/kv/commonKeyValueDao.js +32 -54
  19. package/dist/kv/commonKeyValueDaoMemoCache.d.ts +2 -2
  20. package/dist/query/dbQuery.d.ts +2 -2
  21. package/dist/query/dbQuery.js +2 -2
  22. package/dist/testing/daoTest.js +26 -0
  23. package/dist/testing/dbTest.js +21 -23
  24. package/dist/testing/keyValueDBTest.js +22 -7
  25. package/dist/testing/keyValueDaoTest.d.ts +2 -2
  26. package/dist/testing/keyValueDaoTest.js +31 -8
  27. package/package.json +1 -1
  28. package/src/adapter/cachedb/cache.db.ts +6 -5
  29. package/src/adapter/file/file.db.ts +2 -1
  30. package/src/adapter/inmemory/inMemory.db.ts +29 -18
  31. package/src/adapter/inmemory/inMemoryKeyValueDB.ts +16 -16
  32. package/src/base.common.db.ts +18 -5
  33. package/src/common.db.ts +36 -17
  34. package/src/commondao/common.dao.ts +46 -11
  35. package/src/db.model.ts +0 -19
  36. package/src/kv/commonKeyValueDB.ts +19 -11
  37. package/src/kv/commonKeyValueDao.ts +53 -86
  38. package/src/kv/commonKeyValueDaoMemoCache.ts +2 -2
  39. package/src/query/dbQuery.ts +2 -3
  40. package/src/testing/daoTest.ts +28 -0
  41. package/src/testing/dbTest.ts +22 -28
  42. package/src/testing/keyValueDBTest.ts +31 -14
  43. package/src/testing/keyValueDaoTest.ts +37 -12
@@ -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>;
@@ -1,6 +1,6 @@
1
1
  import { AsyncMapper, BaseDBEntity, ObjectWithId } from '@naturalcycles/js-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { CommonDaoOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, DBPatch } from '..';
3
+ import { CommonDaoOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions } from '..';
4
4
  import { CommonDao } from '../commondao/common.dao';
5
5
  import { RunQueryResult } from '../db.model';
6
6
  /**
@@ -99,7 +99,7 @@ export declare class RunnableDBQuery<BM extends BaseDBEntity, DBM extends BaseDB
99
99
  runQueryExtended(opt?: CommonDaoOptions): Promise<RunQueryResult<BM>>;
100
100
  runQueryExtendedAsDBM(opt?: CommonDaoOptions): Promise<RunQueryResult<DBM>>;
101
101
  runQueryCount(opt?: CommonDaoOptions): Promise<number>;
102
- updateByQuery(patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
102
+ patchByQuery(patch: Partial<DBM>, opt?: CommonDaoOptions): Promise<number>;
103
103
  streamQueryForEach(mapper: AsyncMapper<BM, void>, opt?: CommonDaoStreamForEachOptions<BM>): Promise<void>;
104
104
  streamQueryAsDBMForEach(mapper: AsyncMapper<DBM, void>, opt?: CommonDaoStreamForEachOptions<DBM>): Promise<void>;
105
105
  streamQuery(opt?: CommonDaoStreamOptions<BM>): ReadableTyped<BM>;
@@ -165,8 +165,8 @@ class RunnableDBQuery extends DBQuery {
165
165
  async runQueryCount(opt) {
166
166
  return await this.dao.runQueryCount(this, opt);
167
167
  }
168
- async updateByQuery(patch, opt) {
169
- return await this.dao.updateByQuery(this, patch, opt);
168
+ async patchByQuery(patch, opt) {
169
+ return await this.dao.patchByQuery(this, patch, opt);
170
170
  }
171
171
  async streamQueryForEach(mapper, opt) {
172
172
  await this.dao.streamQueryForEach(this, mapper, opt);
@@ -99,6 +99,32 @@ function runCommonDaoTest(db, quirks = {}) {
99
99
  expect(items[0]).toMatchObject(clone);
100
100
  (0, dbTest_1.expectMatch)(expectedItems, itemsSaved, quirks);
101
101
  });
102
+ if (support.increment) {
103
+ test('increment', async () => {
104
+ await dao.incrementBatch('k3', { id1: 1, id2: 2 });
105
+ let rows = await dao.query().runQuery();
106
+ rows = (0, js_lib_1._sortBy)(rows, r => r.id);
107
+ const expected = expectedItems.map(r => {
108
+ if (r.id === 'id1') {
109
+ return {
110
+ ...r,
111
+ k3: r.k3 + 1,
112
+ };
113
+ }
114
+ if (r.id === 'id2') {
115
+ return {
116
+ ...r,
117
+ k3: r.k3 + 2,
118
+ };
119
+ }
120
+ return r;
121
+ });
122
+ (0, dbTest_1.expectMatch)(expected, rows, quirks);
123
+ // reset the changes
124
+ await dao.increment('k3', 'id1', -1);
125
+ await dao.increment('k3', 'id2', -2);
126
+ });
127
+ }
102
128
  // GET not empty
103
129
  test('getByIds all items', async () => {
104
130
  const rows = await dao.getByIds(items.map(i => i.id).concat('abcd'));
@@ -4,7 +4,6 @@ exports.runCommonDBTest = runCommonDBTest;
4
4
  exports.expectMatch = expectMatch;
5
5
  const js_lib_1 = require("@naturalcycles/js-lib");
6
6
  const common_db_1 = require("../common.db");
7
- const db_model_1 = require("../db.model");
8
7
  const dbQuery_1 = require("../query/dbQuery");
9
8
  const test_model_1 = require("./test.model");
10
9
  function runCommonDBTest(db, quirks = {}) {
@@ -236,8 +235,8 @@ function runCommonDBTest(db, quirks = {}) {
236
235
  expectMatch(expected, rows, quirks);
237
236
  });
238
237
  }
239
- if (support.updateByQuery) {
240
- test('updateByQuery simple', async () => {
238
+ if (support.patchByQuery) {
239
+ test('patchByQuery', async () => {
241
240
  // cleanup, reset initial data
242
241
  await db.deleteByQuery(queryAll());
243
242
  await db.saveBatch(test_model_1.TEST_TABLE, items);
@@ -245,7 +244,7 @@ function runCommonDBTest(db, quirks = {}) {
245
244
  k3: 5,
246
245
  k2: 'abc',
247
246
  };
248
- await db.updateByQuery(dbQuery_1.DBQuery.create(test_model_1.TEST_TABLE).filterEq('even', true), patch);
247
+ await db.patchByQuery(dbQuery_1.DBQuery.create(test_model_1.TEST_TABLE).filterEq('even', true), patch);
249
248
  const { rows } = await db.runQuery(queryAll());
250
249
  const expected = items.map(r => {
251
250
  if (r.even) {
@@ -255,26 +254,25 @@ function runCommonDBTest(db, quirks = {}) {
255
254
  });
256
255
  expectMatch(expected, rows, quirks);
257
256
  });
258
- if (support.dbIncrement) {
259
- test('updateByQuery DBIncrement', async () => {
260
- // cleanup, reset initial data
261
- await db.deleteByQuery(queryAll());
262
- await db.saveBatch(test_model_1.TEST_TABLE, items);
263
- const patch = {
264
- k3: db_model_1.DBIncrement.of(1),
265
- k2: 'abcd',
266
- };
267
- await db.updateByQuery(dbQuery_1.DBQuery.create(test_model_1.TEST_TABLE).filterEq('even', true), patch);
268
- const { rows } = await db.runQuery(queryAll());
269
- const expected = items.map(r => {
270
- if (r.even) {
271
- return { ...r, ...patch, k3: (r.k3 || 0) + 1 };
272
- }
273
- return r;
274
- });
275
- expectMatch(expected, rows, quirks);
257
+ }
258
+ if (support.increment) {
259
+ test('incrementBatch', async () => {
260
+ // cleanup, reset initial data
261
+ await db.deleteByQuery(queryAll());
262
+ await db.saveBatch(test_model_1.TEST_TABLE, items);
263
+ await db.incrementBatch(test_model_1.TEST_TABLE, 'k3', { id1: 1, id2: 2 });
264
+ const { rows } = await db.runQuery(queryAll());
265
+ const expected = items.map(r => {
266
+ if (r.id === 'id1') {
267
+ return { ...r, k3: 2 };
268
+ }
269
+ if (r.id === 'id2') {
270
+ return { ...r, k3: 4 };
271
+ }
272
+ return r;
276
273
  });
277
- }
274
+ expectMatch(expected, rows, quirks);
275
+ });
278
276
  }
279
277
  if (support.queries) {
280
278
  test('cleanup', async () => {
@@ -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,
@@ -86,17 +89,29 @@ function runCommonKeyValueDBTest(db) {
86
89
  expect(results).toEqual([]);
87
90
  });
88
91
  if (support.increment) {
92
+ const id = 'nonExistingField';
93
+ const id2 = 'nonExistingField2';
89
94
  test('increment on a non-existing field should set the value to 1', async () => {
90
- const result = await db.increment(test_model_1.TEST_TABLE, 'nonExistingField');
91
- expect(result).toBe(1);
95
+ const result = await db.incrementBatch(test_model_1.TEST_TABLE, [[id, 1]]);
96
+ expect(result).toEqual([[id, 1]]);
92
97
  });
93
98
  test('increment on a existing field should increase the value by one', async () => {
94
- const result = await db.increment(test_model_1.TEST_TABLE, 'nonExistingField');
95
- expect(result).toBe(2);
99
+ const result = await db.incrementBatch(test_model_1.TEST_TABLE, [[id, 1]]);
100
+ expect(result).toEqual([[id, 2]]);
96
101
  });
97
102
  test('increment should increase the value by the specified amount', async () => {
98
- const result = await db.increment(test_model_1.TEST_TABLE, 'nonExistingField', 2);
99
- expect(result).toBe(4);
103
+ const result = await db.incrementBatch(test_model_1.TEST_TABLE, [[id, 2]]);
104
+ expect(result).toEqual([[id, 4]]);
105
+ });
106
+ test('increment 2 ids at the same time', async () => {
107
+ const result = await db.incrementBatch(test_model_1.TEST_TABLE, [
108
+ [id, 1],
109
+ [id2, 2],
110
+ ]);
111
+ expect(Object.fromEntries(result)).toEqual({
112
+ [id]: 5,
113
+ [id2]: 2,
114
+ });
100
115
  });
101
116
  }
102
117
  }
@@ -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.21.0",
48
+ "version": "9.22.1",
49
49
  "description": "Lowest Common Denominator API to supported Databases",
50
50
  "keywords": [
51
51
  "db",
@@ -9,7 +9,7 @@ import {
9
9
  import { ReadableTyped } from '@naturalcycles/nodejs-lib'
10
10
  import { BaseCommonDB } from '../../base.common.db'
11
11
  import { CommonDB, commonDBFullSupport, CommonDBSupport } from '../../common.db'
12
- import { DBPatch, RunQueryResult } from '../../db.model'
12
+ import { RunQueryResult } from '../../db.model'
13
13
  import { DBQuery } from '../../query/dbQuery'
14
14
  import {
15
15
  CacheDBCfg,
@@ -29,6 +29,7 @@ export class CacheDB extends BaseCommonDB implements CommonDB {
29
29
  override support: CommonDBSupport = {
30
30
  ...commonDBFullSupport,
31
31
  transactions: false,
32
+ increment: false,
32
33
  }
33
34
 
34
35
  constructor(cfg: CacheDBCfg) {
@@ -271,19 +272,19 @@ export class CacheDB extends BaseCommonDB implements CommonDB {
271
272
  return deletedIds
272
273
  }
273
274
 
274
- override async updateByQuery<ROW extends ObjectWithId>(
275
+ override async patchByQuery<ROW extends ObjectWithId>(
275
276
  q: DBQuery<ROW>,
276
- patch: DBPatch<ROW>,
277
+ patch: Partial<ROW>,
277
278
  opt: CacheDBOptions = {},
278
279
  ): Promise<number> {
279
280
  let updated: number | undefined
280
281
 
281
282
  if (!opt.onlyCache && !this.cfg.onlyCache) {
282
- updated = await this.cfg.downstreamDB.updateByQuery(q, patch, opt)
283
+ updated = await this.cfg.downstreamDB.patchByQuery(q, patch, opt)
283
284
  }
284
285
 
285
286
  if (!opt.skipCache && !this.cfg.skipCache) {
286
- const cacheResult = this.cfg.cacheDB.updateByQuery(q, patch, opt)
287
+ const cacheResult = this.cfg.cacheDB.patchByQuery(q, patch, opt)
287
288
  if (this.cfg.awaitCache) updated ??= await cacheResult
288
289
  }
289
290
 
@@ -45,9 +45,10 @@ export class FileDB extends BaseCommonDB implements CommonDB {
45
45
  bufferValues: false, // todo: implement
46
46
  insertSaveMethod: false,
47
47
  updateSaveMethod: false,
48
- updateByQuery: false,
48
+ patchByQuery: false,
49
49
  createTable: false,
50
50
  transactions: false, // todo
51
+ increment: false,
51
52
  }
52
53
 
53
54
  constructor(cfg: FileDBCfg) {
@@ -3,9 +3,12 @@ import {
3
3
  _assert,
4
4
  _by,
5
5
  _deepCopy,
6
+ _isEmptyObject,
6
7
  _since,
7
8
  _sortObjectDeep,
9
+ _stringMapEntries,
8
10
  _stringMapValues,
11
+ AnyObjectWithId,
9
12
  CommonLogger,
10
13
  generateJsonSchemaFromData,
11
14
  JsonSchemaObject,
@@ -27,9 +30,7 @@ import {
27
30
  commonDBFullSupport,
28
31
  CommonDBTransactionOptions,
29
32
  CommonDBType,
30
- DBIncrement,
31
33
  DBOperation,
32
- DBPatch,
33
34
  DBTransactionFn,
34
35
  queryInMemory,
35
36
  } from '../..'
@@ -113,7 +114,7 @@ export class InMemoryDB implements CommonDB {
113
114
  cfg: InMemoryDBCfg
114
115
 
115
116
  // data[table][id] > {id: 'a', created: ... }
116
- data: StringMap<StringMap<ObjectWithId>> = {}
117
+ data: StringMap<StringMap<AnyObjectWithId>> = {}
117
118
 
118
119
  /**
119
120
  * Returns internal "Data snapshot".
@@ -233,25 +234,14 @@ export class InMemoryDB implements CommonDB {
233
234
  return count
234
235
  }
235
236
 
236
- async updateByQuery<ROW extends ObjectWithId>(
237
+ async patchByQuery<ROW extends ObjectWithId>(
237
238
  q: DBQuery<ROW>,
238
- patch: DBPatch<ROW>,
239
+ patch: Partial<ROW>,
239
240
  ): Promise<number> {
240
- const patchEntries = Object.entries(patch)
241
- if (!patchEntries.length) return 0
242
-
241
+ if (_isEmptyObject(patch)) return 0
243
242
  const table = this.cfg.tablesPrefix + q.table
244
243
  const rows = queryInMemory(q, Object.values(this.data[table] || {}) as ROW[])
245
- rows.forEach((row: any) => {
246
- patchEntries.forEach(([k, v]) => {
247
- if (v instanceof DBIncrement) {
248
- row[k] = (row[k] || 0) + v.amount
249
- } else {
250
- row[k] = v
251
- }
252
- })
253
- })
254
-
244
+ rows.forEach(row => Object.assign(row, patch))
255
245
  return rows.length
256
246
  }
257
247
 
@@ -294,6 +284,27 @@ export class InMemoryDB implements CommonDB {
294
284
  }
295
285
  }
296
286
 
287
+ async incrementBatch(
288
+ table: string,
289
+ prop: string,
290
+ incrementMap: StringMap<number>,
291
+ _opt?: CommonDBOptions,
292
+ ): Promise<StringMap<number>> {
293
+ const tbl = this.cfg.tablesPrefix + table
294
+ this.data[tbl] ||= {}
295
+
296
+ const result: StringMap<number> = {}
297
+
298
+ for (const [id, by] of _stringMapEntries(incrementMap)) {
299
+ this.data[tbl][id] ||= { id }
300
+ const newValue = ((this.data[tbl][id][prop] as number) || 0) + by
301
+ this.data[tbl][id][prop] = newValue
302
+ result[id] = newValue
303
+ }
304
+
305
+ return result
306
+ }
307
+
297
308
  /**
298
309
  * Flushes all tables (all namespaces) at once.
299
310
  */