@naturalcycles/db-lib 9.20.0 → 9.22.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.
Files changed (41) 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 +5 -0
  7. package/dist/adapter/inmemory/inMemoryKeyValueDB.js +17 -0
  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 -29
  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 +36 -10
  17. package/dist/kv/commonKeyValueDB.js +5 -0
  18. package/dist/kv/commonKeyValueDao.d.ts +8 -5
  19. package/dist/kv/commonKeyValueDao.js +9 -0
  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 +32 -15
  25. package/dist/testing/keyValueDaoTest.js +15 -12
  26. package/package.json +1 -1
  27. package/src/adapter/cachedb/cache.db.ts +6 -5
  28. package/src/adapter/file/file.db.ts +2 -1
  29. package/src/adapter/inmemory/inMemory.db.ts +29 -18
  30. package/src/adapter/inmemory/inMemoryKeyValueDB.ts +26 -2
  31. package/src/base.common.db.ts +18 -5
  32. package/src/common.db.ts +36 -34
  33. package/src/commondao/common.dao.ts +46 -11
  34. package/src/db.model.ts +0 -19
  35. package/src/kv/commonKeyValueDB.ts +45 -12
  36. package/src/kv/commonKeyValueDao.ts +22 -8
  37. package/src/query/dbQuery.ts +2 -3
  38. package/src/testing/daoTest.ts +28 -0
  39. package/src/testing/dbTest.ts +22 -28
  40. package/src/testing/keyValueDBTest.ts +37 -17
  41. package/src/testing/keyValueDaoTest.ts +16 -12
@@ -22,6 +22,7 @@ import {
22
22
  ObjectWithId,
23
23
  pMap,
24
24
  SKIP,
25
+ StringMap,
25
26
  UnixTimestampMillisNumber,
26
27
  Unsaved,
27
28
  ZodSchema,
@@ -44,7 +45,7 @@ import {
44
45
  writableVoid,
45
46
  } from '@naturalcycles/nodejs-lib'
46
47
  import { DBLibError } from '../cnst'
47
- import { CommonDBTransactionOptions, DBPatch, DBTransaction, RunQueryResult } from '../db.model'
48
+ import { CommonDBTransactionOptions, DBTransaction, RunQueryResult } from '../db.model'
48
49
  import { DBQuery, RunnableDBQuery } from '../query/dbQuery'
49
50
  import {
50
51
  CommonDaoCfg,
@@ -1068,31 +1069,65 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
1068
1069
  return deleted
1069
1070
  }
1070
1071
 
1071
- async updateById(id: ID, patch: DBPatch<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
1072
- return await this.updateByQuery(this.query().filterEq('id', id), patch, opt)
1073
- }
1074
-
1075
- async updateByIds(ids: ID[], patch: DBPatch<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
1072
+ async patchByIds(ids: ID[], patch: Partial<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
1076
1073
  if (!ids.length) return 0
1077
- return await this.updateByQuery(this.query().filterIn('id', ids), patch, opt)
1074
+ return await this.patchByQuery(this.query().filterIn('id', ids), patch, opt)
1078
1075
  }
1079
1076
 
1080
- async updateByQuery(
1077
+ async patchByQuery(
1081
1078
  q: DBQuery<DBM>,
1082
- patch: DBPatch<DBM>,
1079
+ patch: Partial<DBM>,
1083
1080
  opt: CommonDaoOptions = {},
1084
1081
  ): Promise<number> {
1085
1082
  this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
1086
1083
  this.requireWriteAccess()
1087
1084
  this.requireObjectMutability(opt)
1088
1085
  q.table = opt.table || q.table
1089
- const op = `updateByQuery(${q.pretty()})`
1086
+ const op = `patchByQuery(${q.pretty()})`
1090
1087
  const started = this.logStarted(op, q.table)
1091
- const updated = await this.cfg.db.updateByQuery(q, patch, opt)
1088
+ const updated = await this.cfg.db.patchByQuery(q, patch, opt)
1092
1089
  this.logSaveResult(started, op, q.table)
1093
1090
  return updated
1094
1091
  }
1095
1092
 
1093
+ /**
1094
+ * Caveat: it doesn't update created/updated props.
1095
+ *
1096
+ * @experimental
1097
+ */
1098
+ async increment(prop: keyof DBM, id: ID, by = 1, opt: CommonDaoOptions = {}): Promise<number> {
1099
+ this.requireWriteAccess()
1100
+ this.requireObjectMutability(opt)
1101
+ const { table } = this.cfg
1102
+ const op = `increment`
1103
+ const started = this.logStarted(op, table)
1104
+ const result = await this.cfg.db.incrementBatch(table, prop as string, {
1105
+ [id as string]: by,
1106
+ })
1107
+ this.logSaveResult(started, op, table)
1108
+ return result[id as string]!
1109
+ }
1110
+
1111
+ /**
1112
+ * Caveat: it doesn't update created/updated props.
1113
+ *
1114
+ * @experimental
1115
+ */
1116
+ async incrementBatch(
1117
+ prop: keyof DBM,
1118
+ incrementMap: StringMap<number>,
1119
+ opt: CommonDaoOptions = {},
1120
+ ): Promise<StringMap<number>> {
1121
+ this.requireWriteAccess()
1122
+ this.requireObjectMutability(opt)
1123
+ const { table } = this.cfg
1124
+ const op = `incrementBatch`
1125
+ const started = this.logStarted(op, table)
1126
+ const result = await this.cfg.db.incrementBatch(table, prop as string, incrementMap)
1127
+ this.logSaveResult(started, op, table)
1128
+ return result
1129
+ }
1130
+
1096
1131
  // CONVERSIONS
1097
1132
 
1098
1133
  async dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): Promise<undefined>
package/src/db.model.ts CHANGED
@@ -113,22 +113,3 @@ export enum DBModelType {
113
113
  DBM = 'DBM',
114
114
  BM = 'BM',
115
115
  }
116
-
117
- /**
118
- * Allows to construct a query similar to:
119
- *
120
- * UPDATE table SET A = A + 1
121
- *
122
- * In this case DBIncement.of(1) will be needed.
123
- */
124
- export class DBIncrement {
125
- private constructor(public amount: number) {}
126
-
127
- static of(amount: number): DBIncrement {
128
- return new DBIncrement(amount)
129
- }
130
- }
131
-
132
- export type DBPatch<ROW extends ObjectWithId> = Partial<
133
- Record<keyof ROW, ROW[keyof ROW] | DBIncrement>
134
- >
@@ -1,23 +1,18 @@
1
- import { UnixTimestampNumber } from '@naturalcycles/js-lib'
1
+ import { StringMap, UnixTimestampNumber } from '@naturalcycles/js-lib'
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib'
3
3
  import { CommonDBCreateOptions } from '../db.model'
4
4
 
5
- export type KeyValueDBTuple = [key: string, value: Buffer]
6
-
7
- export interface CommonKeyValueDBSaveBatchOptions {
8
- /**
9
- * If set (and if it's implemented by the driver) - will set expiry TTL for each key of the batch.
10
- * E.g EXAT in Redis.
11
- */
12
- expireAt?: UnixTimestampNumber
13
- }
14
-
15
5
  /**
16
6
  * Common interface for Key-Value database implementations.
17
7
  *
18
8
  * @experimental
19
9
  */
20
10
  export interface CommonKeyValueDB {
11
+ /**
12
+ * Manifest of supported features.
13
+ */
14
+ support: CommonKeyValueDBSupport
15
+
21
16
  /**
22
17
  * Check that DB connection is working properly.
23
18
  */
@@ -51,11 +46,49 @@ export interface CommonKeyValueDB {
51
46
  count: (table: string) => Promise<number>
52
47
 
53
48
  /**
54
- *
55
49
  * Increments the value of a key in a table by a given amount.
56
50
  * Default increment is 1 when `by` is not provided.
57
51
  *
58
52
  * Returns the new value.
53
+ *
54
+ * @experimental
59
55
  */
60
56
  increment: (table: string, id: string, by?: number) => Promise<number>
57
+
58
+ /**
59
+ * Perform a batch of Increment operations.
60
+ * Given incrementMap, increment each key of it by the given amount (value of the map).
61
+ *
62
+ * Example:
63
+ * { key1: 2, key2: 3 }
64
+ * would increment `key1` by 2, and `key2` by 3.
65
+ *
66
+ * Returns the incrementMap with the same keys and updated values.
67
+ *
68
+ * @experimental
69
+ */
70
+ incrementBatch: (table: string, incrementMap: StringMap<number>) => Promise<StringMap<number>>
71
+ }
72
+
73
+ export type KeyValueDBTuple = [key: string, value: Buffer]
74
+
75
+ export interface CommonKeyValueDBSaveBatchOptions {
76
+ /**
77
+ * If set (and if it's implemented by the driver) - will set expiry TTL for each key of the batch.
78
+ * E.g EXAT in Redis.
79
+ */
80
+ expireAt?: UnixTimestampNumber
81
+ }
82
+
83
+ /**
84
+ * Manifest of supported features.
85
+ */
86
+ export interface CommonKeyValueDBSupport {
87
+ count?: boolean
88
+ increment?: boolean
89
+ }
90
+
91
+ export const commonKeyValueDBFullSupport: CommonKeyValueDBSupport = {
92
+ count: true,
93
+ increment: true,
61
94
  }
@@ -8,7 +8,7 @@ import {
8
8
  KeyValueDBTuple,
9
9
  } from './commonKeyValueDB'
10
10
 
11
- export interface CommonKeyValueDaoCfg<T> {
11
+ export interface CommonKeyValueDaoCfg<V> {
12
12
  db: CommonKeyValueDB
13
13
 
14
14
  table: string
@@ -35,9 +35,9 @@ export interface CommonKeyValueDaoCfg<T> {
35
35
  logStarted?: boolean
36
36
 
37
37
  hooks?: {
38
- mapValueToBuffer?: (v: T) => Promise<Buffer>
39
- mapBufferToValue?: (b: Buffer) => Promise<T>
40
- beforeCreate?: (v: Partial<T>) => Partial<T>
38
+ mapValueToBuffer?: (v: V) => Promise<Buffer>
39
+ mapBufferToValue?: (b: Buffer) => Promise<V>
40
+ beforeCreate?: (v: Partial<V>) => Partial<V>
41
41
  }
42
42
 
43
43
  /**
@@ -203,8 +203,8 @@ export class CommonKeyValueDao<V, K extends string = string> {
203
203
  await this.cfg.db.deleteByIds(this.cfg.table, [id])
204
204
  }
205
205
 
206
- streamIds(limit?: number): ReadableTyped<string> {
207
- return this.cfg.db.streamIds(this.cfg.table, limit)
206
+ streamIds(limit?: number): ReadableTyped<K> {
207
+ return this.cfg.db.streamIds(this.cfg.table, limit) as ReadableTyped<K>
208
208
  }
209
209
 
210
210
  streamValues(limit?: number): ReadableTyped<V> {
@@ -236,10 +236,12 @@ export class CommonKeyValueDao<V, K extends string = string> {
236
236
  return this.cfg.db.streamEntries(this.cfg.table, limit) as ReadableTyped<KeyValueTuple<K, V>>
237
237
  }
238
238
 
239
- return this.cfg.db.streamEntries(this.cfg.table, limit).flatMap(
239
+ return (
240
+ this.cfg.db.streamEntries(this.cfg.table, limit) as ReadableTyped<KeyValueTuple<K, Buffer>>
241
+ ).flatMap(
240
242
  async ([id, buf]) => {
241
243
  try {
242
- return [[id as K, await mapBufferToValue(buf)]]
244
+ return [[id, await mapBufferToValue(buf)]]
243
245
  } catch (err) {
244
246
  this.cfg.logger.error(err)
245
247
  return [] // SKIP
@@ -251,6 +253,18 @@ export class CommonKeyValueDao<V, K extends string = string> {
251
253
  )
252
254
  }
253
255
 
256
+ async getAllKeys(limit?: number): Promise<K[]> {
257
+ return await this.streamIds(limit).toArray()
258
+ }
259
+
260
+ async getAllValues(limit?: number): Promise<V[]> {
261
+ return await this.streamValues(limit).toArray()
262
+ }
263
+
264
+ async getAllEntries(limit?: number): Promise<KeyValueTuple<K, V>[]> {
265
+ return await this.streamEntries(limit).toArray()
266
+ }
267
+
254
268
  /**
255
269
  * Increments the `id` field by the amount specified in `by`,
256
270
  * or by 1 if `by` is not specified.
@@ -11,7 +11,6 @@ import {
11
11
  CommonDaoStreamDeleteOptions,
12
12
  CommonDaoStreamForEachOptions,
13
13
  CommonDaoStreamOptions,
14
- DBPatch,
15
14
  } from '..'
16
15
  import { CommonDao } from '../commondao/common.dao'
17
16
  import { RunQueryResult } from '../db.model'
@@ -276,8 +275,8 @@ export class RunnableDBQuery<
276
275
  return await this.dao.runQueryCount(this, opt)
277
276
  }
278
277
 
279
- async updateByQuery(patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number> {
280
- return await this.dao.updateByQuery(this, patch, opt)
278
+ async patchByQuery(patch: Partial<DBM>, opt?: CommonDaoOptions): Promise<number> {
279
+ return await this.dao.patchByQuery(this, patch, opt)
281
280
  }
282
281
 
283
282
  async streamQueryForEach(
@@ -129,6 +129,34 @@ export function runCommonDaoTest(db: CommonDB, quirks: CommonDBImplementationQui
129
129
  expectMatch(expectedItems, itemsSaved, quirks)
130
130
  })
131
131
 
132
+ if (support.increment) {
133
+ test('increment', async () => {
134
+ await dao.incrementBatch('k3', { id1: 1, id2: 2 })
135
+ let rows = await dao.query().runQuery()
136
+ rows = _sortBy(rows, r => r.id)
137
+ const expected = expectedItems.map(r => {
138
+ if (r.id === 'id1') {
139
+ return {
140
+ ...r,
141
+ k3: r.k3! + 1,
142
+ }
143
+ }
144
+ if (r.id === 'id2') {
145
+ return {
146
+ ...r,
147
+ k3: r.k3! + 2,
148
+ }
149
+ }
150
+ return r
151
+ })
152
+ expectMatch(expected, rows, quirks)
153
+
154
+ // reset the changes
155
+ await dao.increment('k3', 'id1', -1)
156
+ await dao.increment('k3', 'id2', -2)
157
+ })
158
+ }
159
+
132
160
  // GET not empty
133
161
  test('getByIds all items', async () => {
134
162
  const rows = await dao.getByIds(items.map(i => i.id).concat('abcd'))
@@ -1,6 +1,5 @@
1
1
  import { _deepFreeze, _filterObject, _pick, _sortBy, pMap } from '@naturalcycles/js-lib'
2
2
  import { CommonDB, CommonDBType } from '../common.db'
3
- import { DBIncrement, DBPatch } from '../db.model'
4
3
  import { DBQuery } from '../query/dbQuery'
5
4
  import {
6
5
  createTestItemDBM,
@@ -317,18 +316,18 @@ export function runCommonDBTest(db: CommonDB, quirks: CommonDBImplementationQuir
317
316
  })
318
317
  }
319
318
 
320
- if (support.updateByQuery) {
321
- test('updateByQuery simple', async () => {
319
+ if (support.patchByQuery) {
320
+ test('patchByQuery', async () => {
322
321
  // cleanup, reset initial data
323
322
  await db.deleteByQuery(queryAll())
324
323
  await db.saveBatch(TEST_TABLE, items)
325
324
 
326
- const patch: DBPatch<TestItemDBM> = {
325
+ const patch: Partial<TestItemDBM> = {
327
326
  k3: 5,
328
327
  k2: 'abc',
329
328
  }
330
329
 
331
- await db.updateByQuery(DBQuery.create<TestItemDBM>(TEST_TABLE).filterEq('even', true), patch)
330
+ await db.patchByQuery(DBQuery.create<TestItemDBM>(TEST_TABLE).filterEq('even', true), patch)
332
331
 
333
332
  const { rows } = await db.runQuery(queryAll())
334
333
  const expected = items.map(r => {
@@ -339,33 +338,28 @@ export function runCommonDBTest(db: CommonDB, quirks: CommonDBImplementationQuir
339
338
  })
340
339
  expectMatch(expected, rows, quirks)
341
340
  })
341
+ }
342
342
 
343
- if (support.dbIncrement) {
344
- test('updateByQuery DBIncrement', async () => {
345
- // cleanup, reset initial data
346
- await db.deleteByQuery(queryAll())
347
- await db.saveBatch(TEST_TABLE, items)
348
-
349
- const patch: DBPatch<TestItemDBM> = {
350
- k3: DBIncrement.of(1),
351
- k2: 'abcd',
352
- }
343
+ if (support.increment) {
344
+ test('incrementBatch', async () => {
345
+ // cleanup, reset initial data
346
+ await db.deleteByQuery(queryAll())
347
+ await db.saveBatch(TEST_TABLE, items)
353
348
 
354
- await db.updateByQuery(
355
- DBQuery.create<TestItemDBM>(TEST_TABLE).filterEq('even', true),
356
- patch,
357
- )
349
+ await db.incrementBatch(TEST_TABLE, 'k3', { id1: 1, id2: 2 })
358
350
 
359
- const { rows } = await db.runQuery(queryAll())
360
- const expected = items.map(r => {
361
- if (r.even) {
362
- return { ...r, ...patch, k3: (r.k3 || 0) + 1 }
363
- }
364
- return r
365
- })
366
- expectMatch(expected, rows, quirks)
351
+ const { rows } = await db.runQuery(queryAll())
352
+ const expected = items.map(r => {
353
+ if (r.id === 'id1') {
354
+ return { ...r, k3: 2 }
355
+ }
356
+ if (r.id === 'id2') {
357
+ return { ...r, k3: 4 }
358
+ }
359
+ return r
367
360
  })
368
- }
361
+ expectMatch(expected, rows, quirks)
362
+ })
369
363
  }
370
364
 
371
365
  if (support.queries) {
@@ -21,6 +21,8 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
21
21
  await db.deleteByIds(TEST_TABLE, ids)
22
22
  })
23
23
 
24
+ const { support } = db
25
+
24
26
  test('ping', async () => {
25
27
  await db.ping()
26
28
  })
@@ -50,9 +52,11 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
50
52
  expect(entries).toEqual(testEntries)
51
53
  })
52
54
 
53
- test('count should be 3', async () => {
54
- expect(await db.count(TEST_TABLE)).toBe(3)
55
- })
55
+ if (support.count) {
56
+ test('count should be 3', async () => {
57
+ expect(await db.count(TEST_TABLE)).toBe(3)
58
+ })
59
+ }
56
60
 
57
61
  test('streamIds', async () => {
58
62
  const ids = await db.streamIds(TEST_TABLE).toArray()
@@ -100,18 +104,34 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
100
104
  expect(results).toEqual([])
101
105
  })
102
106
 
103
- test('increment on a non-existing field should set the value to 1', async () => {
104
- const result = await db.increment(TEST_TABLE, 'nonExistingField')
105
- expect(result).toBe(1)
106
- })
107
-
108
- test('increment on a existing field should increase the value by one', async () => {
109
- const result = await db.increment(TEST_TABLE, 'nonExistingField')
110
- expect(result).toBe(2)
111
- })
112
-
113
- test('increment should increase the value by the specified amount', async () => {
114
- const result = await db.increment(TEST_TABLE, 'nonExistingField', 2)
115
- expect(result).toBe(4)
116
- })
107
+ if (support.increment) {
108
+ const id = 'nonExistingField'
109
+ const id2 = 'nonExistingField2'
110
+
111
+ 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)
114
+ })
115
+
116
+ 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)
119
+ })
120
+
121
+ 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)
124
+ })
125
+
126
+ 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({
132
+ [id]: 5,
133
+ [id2]: 2,
134
+ })
135
+ })
136
+ }
117
137
  }
@@ -20,6 +20,8 @@ export function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void {
20
20
  await dao.deleteByIds(ids)
21
21
  })
22
22
 
23
+ const { support } = dao.cfg.db
24
+
23
25
  test('ping', async () => {
24
26
  await dao.ping()
25
27
  })
@@ -91,18 +93,20 @@ export function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void {
91
93
  expect(results).toEqual([])
92
94
  })
93
95
 
94
- test('increment on a non-existing field should set the value to 1', async () => {
95
- const result = await dao.increment('nonExistingField')
96
- expect(result).toBe(1)
97
- })
96
+ if (support.increment) {
97
+ test('increment on a non-existing field should set the value to 1', async () => {
98
+ const result = await dao.increment('nonExistingField')
99
+ expect(result).toBe(1)
100
+ })
98
101
 
99
- test('increment on a existing field should increase the value by one', async () => {
100
- const result = await dao.increment('nonExistingField')
101
- expect(result).toBe(2)
102
- })
102
+ test('increment on a existing field should increase the value by one', async () => {
103
+ const result = await dao.increment('nonExistingField')
104
+ expect(result).toBe(2)
105
+ })
103
106
 
104
- test('increment should increase the value by the specified amount', async () => {
105
- const result = await dao.increment('nonExistingField', 2)
106
- expect(result).toBe(4)
107
- })
107
+ test('increment should increase the value by the specified amount', async () => {
108
+ const result = await dao.increment('nonExistingField', 2)
109
+ expect(result).toBe(4)
110
+ })
111
+ }
108
112
  }