@naturalcycles/db-lib 8.41.0 → 8.42.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 (36) hide show
  1. package/dist/adapter/cachedb/cache.db.d.ts +3 -1
  2. package/dist/adapter/cachedb/cache.db.js +4 -0
  3. package/dist/adapter/file/file.db.js +24 -4
  4. package/dist/adapter/file/file.db.model.d.ts +1 -1
  5. package/dist/adapter/inmemory/inMemory.db.js +23 -14
  6. package/dist/base.common.db.d.ts +1 -0
  7. package/dist/base.common.db.js +1 -0
  8. package/dist/commondao/common.dao.d.ts +8 -1
  9. package/dist/commondao/common.dao.js +59 -6
  10. package/dist/commondao/common.dao.model.d.ts +10 -0
  11. package/dist/db.model.d.ts +2 -0
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +1 -2
  14. package/dist/testing/daoTest.js +42 -1
  15. package/dist/testing/dbTest.d.ts +1 -0
  16. package/dist/testing/dbTest.js +43 -11
  17. package/dist/timeseries/commonTimeSeriesDao.js +1 -1
  18. package/dist/transaction/dbTransaction.d.ts +7 -0
  19. package/dist/transaction/dbTransaction.js +24 -2
  20. package/dist/transaction/dbTransaction.util.d.ts +3 -3
  21. package/dist/transaction/dbTransaction.util.js +55 -55
  22. package/package.json +2 -3
  23. package/src/adapter/cachedb/cache.db.ts +7 -1
  24. package/src/adapter/file/file.db.model.ts +1 -1
  25. package/src/adapter/file/file.db.ts +28 -5
  26. package/src/adapter/inmemory/inMemory.db.ts +23 -12
  27. package/src/base.common.db.ts +1 -0
  28. package/src/commondao/common.dao.model.ts +11 -0
  29. package/src/commondao/common.dao.ts +80 -8
  30. package/src/db.model.ts +2 -0
  31. package/src/index.ts +1 -2
  32. package/src/testing/daoTest.ts +54 -1
  33. package/src/testing/dbTest.ts +53 -10
  34. package/src/timeseries/commonTimeSeriesDao.ts +1 -1
  35. package/src/transaction/dbTransaction.ts +26 -1
  36. package/src/transaction/dbTransaction.util.ts +29 -33
@@ -2,6 +2,7 @@ import { pDelay, pMap, _filterObject, _pick, _sortBy } from '@naturalcycles/js-l
2
2
  import { readableToArray } from '@naturalcycles/nodejs-lib'
3
3
  import { CommonDB } from '../common.db'
4
4
  import { DBQuery } from '../query/dbQuery'
5
+ import { DBTransaction } from '../transaction/dbTransaction'
5
6
  import {
6
7
  createTestItemDBM,
7
8
  createTestItemsDBM,
@@ -43,6 +44,8 @@ export interface CommonDBImplementationFeatures {
43
44
  * they will return `null` for all missing properties.
44
45
  */
45
46
  documentDB?: boolean
47
+
48
+ transactions?: boolean
46
49
  }
47
50
 
48
51
  /**
@@ -89,6 +92,7 @@ export function runCommonDBTest(
89
92
  bufferSupport = true,
90
93
  nullValues = true,
91
94
  documentDB = true,
95
+ transactions = true,
92
96
  } = features
93
97
 
94
98
  // const {
@@ -190,6 +194,10 @@ export function runCommonDBTest(
190
194
  await db.saveBatch(TEST_TABLE, items)
191
195
  })
192
196
 
197
+ test('saveBatch should throw on null id', async () => {
198
+ await expect(db.saveBatch(TEST_TABLE, [{ ...item1, id: null as any }])).rejects.toThrow()
199
+ })
200
+
193
201
  if (insert) {
194
202
  test('saveBatch INSERT method should throw', async () => {
195
203
  await expect(db.saveBatch(TEST_TABLE, items, { saveMethod: 'insert' })).rejects.toThrow()
@@ -290,12 +298,12 @@ export function runCommonDBTest(
290
298
  // getTables
291
299
  test('getTables, getTableSchema (if supported)', async () => {
292
300
  const tables = await db.getTables()
293
- console.log({ tables })
301
+ // console.log({ tables })
294
302
 
295
303
  if (tableSchemas) {
296
304
  await pMap(tables, async table => {
297
305
  const schema = await db.getTableSchema(table)
298
- console.log(schema)
306
+ // console.log(schema)
299
307
  expect(schema.$id).toBe(`${table}.schema.json`)
300
308
  })
301
309
  }
@@ -327,19 +335,54 @@ export function runCommonDBTest(
327
335
  await db.saveBatch(TEST_TABLE, [item])
328
336
  const [loaded] = await db.getByIds<TestItemDBM>(TEST_TABLE, [item.id])
329
337
  const b1Loaded = loaded!.b1!
330
- console.log({
331
- b11: typeof b1,
332
- b12: typeof b1Loaded,
333
- l1: b1.length,
334
- l2: b1Loaded.length,
335
- b1,
336
- b1Loaded,
337
- })
338
+ // console.log({
339
+ // b11: typeof b1,
340
+ // b12: typeof b1Loaded,
341
+ // l1: b1.length,
342
+ // l2: b1Loaded.length,
343
+ // b1,
344
+ // b1Loaded,
345
+ // })
338
346
  expect(b1Loaded).toEqual(b1)
339
347
  expect(b1Loaded.toString()).toBe(s)
340
348
  })
341
349
  }
342
350
 
351
+ if (transactions) {
352
+ test('transaction happy path', async () => {
353
+ // cleanup
354
+ await db.deleteByQuery(queryAll())
355
+
356
+ // saveBatch [item1, 2, 3]
357
+ // save item3 with k1: k1_mod
358
+ // delete item2
359
+ // remaining: item1, item3_with_k1_mod
360
+ const tx = DBTransaction.create()
361
+ .saveBatch(TEST_TABLE, items)
362
+ .save(TEST_TABLE, { ...items[2]!, k1: 'k1_mod' })
363
+ .deleteById(TEST_TABLE, items[1]!.id)
364
+
365
+ await db.commitTransaction(tx)
366
+
367
+ const { rows } = await db.runQuery(queryAll())
368
+ const expected = [items[0], { ...items[2]!, k1: 'k1_mod' }]
369
+ expectMatch(expected, rows, quirks)
370
+ })
371
+
372
+ test('transaction rollback', async () => {
373
+ // It should fail on id == null
374
+ const tx = DBTransaction.create()
375
+ .deleteById(TEST_TABLE, items[2]!.id)
376
+ .save(TEST_TABLE, { ...items[0]!, k1: 5, id: null as any })
377
+
378
+ await expect(db.commitTransaction(tx)).rejects.toThrow()
379
+
380
+ const { rows } = await db.runQuery(queryAll())
381
+ const expected = [items[0], { ...items[2]!, k1: 'k1_mod' }]
382
+ expectMatch(expected, rows, quirks)
383
+ })
384
+ }
385
+
343
386
  if (querying) {
344
387
  test('cleanup', async () => {
345
388
  // CLEAN UP
@@ -53,7 +53,7 @@ export class CommonTimeSeriesDao {
53
53
  async commitTransaction(ops: TimeSeriesSaveBatchOp[]): Promise<void> {
54
54
  if (!ops.length) return
55
55
 
56
- const tx = new DBTransaction()
56
+ const tx = DBTransaction.create()
57
57
 
58
58
  ops.forEach(op => {
59
59
  const rows: ObjectWithId[] = op.dataPoints.map(([ts, v]) => ({
@@ -6,7 +6,23 @@ import type { CommonDBSaveOptions, DBOperation } from '../db.model'
6
6
  * Convenience class that stores the list of DBOperations and provides a fluent API to add them.
7
7
  */
8
8
  export class DBTransaction {
9
- public ops: DBOperation[] = []
9
+ protected constructor(public ops: DBOperation[] = []) {}
10
+
11
+ /**
12
+ * Convenience method.
13
+ */
14
+ static create(ops: DBOperation[] = []): DBTransaction {
15
+ return new DBTransaction(ops)
16
+ }
17
+
18
+ save<ROW extends ObjectWithId = AnyObjectWithId>(table: string, row: ROW): this {
19
+ this.ops.push({
20
+ type: 'saveBatch',
21
+ table,
22
+ rows: [row],
23
+ })
24
+ return this
25
+ }
10
26
 
11
27
  saveBatch<ROW extends ObjectWithId = AnyObjectWithId>(table: string, rows: ROW[]): this {
12
28
  this.ops.push({
@@ -17,6 +33,15 @@ export class DBTransaction {
17
33
  return this
18
34
  }
19
35
 
36
+ deleteById(table: string, id: string): this {
37
+ this.ops.push({
38
+ type: 'deleteByIds',
39
+ table,
40
+ ids: [id],
41
+ })
42
+ return this
43
+ }
44
+
20
45
  deleteByIds(table: string, ids: string[]): this {
21
46
  this.ops.push({
22
47
  type: 'deleteByIds',
@@ -1,30 +1,32 @@
1
- import { StringMap, _stringMapEntries, ObjectWithId } from '@naturalcycles/js-lib'
2
1
  import type { CommonDB } from '../common.db'
3
- import { CommonDBSaveOptions, DBOperation } from '../db.model'
2
+ import { CommonDBSaveOptions } from '../db.model'
4
3
  import { DBTransaction } from './dbTransaction'
5
4
 
6
5
  /**
7
6
  * Optimizes the Transaction (list of DBOperations) to do less operations.
8
7
  * E.g if you save id1 first and then delete it - this function will turn it into a no-op (self-eliminate).
8
+ * UPD: actually, it will only keep delete, but remove previous ops.
9
9
  *
10
10
  * Currently only takes into account SaveBatch and DeleteByIds ops.
11
- * Output ops are maximum 2 (per table) - save and delete (where order actually doesn't matter, cause ids there will not overlap).
11
+ * Output ops are maximum 1 per entity - save or delete.
12
12
  */
13
+ // Commented out as "overly complicated"
14
+ /*
13
15
  export function mergeDBOperations(ops: DBOperation[]): DBOperation[] {
14
16
  if (ops.length <= 1) return ops // nothing to optimize there
15
17
 
16
18
  // This map will be saved in the end. Null would mean "delete"
17
19
  // saveMap[table][id] => row
18
- const saveMapByTable: StringMap<StringMap<ObjectWithId | null>> = {}
20
+ const data: StringMap<StringMap<ObjectWithId | null>> = {}
19
21
 
20
22
  // Merge ops using `saveMap`
21
23
  ops.forEach(op => {
22
- saveMapByTable[op.table] = saveMapByTable[op.table] || {}
24
+ data[op.table] ||= {}
23
25
 
24
26
  if (op.type === 'saveBatch') {
25
- op.rows.forEach(r => (saveMapByTable[op.table]![r.id] = r))
27
+ op.rows.forEach(r => (data[op.table]![r.id] = r))
26
28
  } else if (op.type === 'deleteByIds') {
27
- op.ids.forEach(id => (saveMapByTable[op.table]![id] = null))
29
+ op.ids.forEach(id => (data[op.table]![id] = null))
28
30
  } else {
29
31
  throw new Error(`DBOperation not supported: ${(op as any).type}`)
30
32
  }
@@ -32,37 +34,31 @@ export function mergeDBOperations(ops: DBOperation[]): DBOperation[] {
32
34
 
33
35
  const resultOps: DBOperation[] = []
34
36
 
35
- _stringMapEntries(saveMapByTable).forEach(([table, saveMap]) => {
36
- const rowsToSave: ObjectWithId[] = []
37
- const idsToDelete: string[] = []
37
+ _stringMapEntries(data).forEach(([table, map]) => {
38
+ const saveOp: DBSaveBatchOperation = {
39
+ type: 'saveBatch',
40
+ table,
41
+ rows: _stringMapValues(map).filter(_isTruthy),
42
+ }
38
43
 
39
- _stringMapEntries(saveMap).forEach(([id, r]) => {
40
- if (r === null) {
41
- idsToDelete.push(id)
42
- } else {
43
- rowsToSave.push(r)
44
- }
45
- })
44
+ if (saveOp.rows.length) {
45
+ resultOps.push(saveOp)
46
+ }
46
47
 
47
- if (rowsToSave.length) {
48
- resultOps.push({
49
- type: 'saveBatch',
50
- table,
51
- rows: rowsToSave,
52
- })
48
+ const deleteOp: DBDeleteByIdsOperation = {
49
+ type: 'deleteByIds',
50
+ table,
51
+ ids: _stringMapEntries(map).filter(([id, row]) => row === null).map(([id]) => id),
53
52
  }
54
53
 
55
- if (idsToDelete.length) {
56
- resultOps.push({
57
- type: 'deleteByIds',
58
- table,
59
- ids: idsToDelete,
60
- })
54
+ if (deleteOp.ids.length) {
55
+ resultOps.push(deleteOp)
61
56
  }
62
57
  })
63
58
 
64
59
  return resultOps
65
60
  }
61
+ */
66
62
 
67
63
  /**
68
64
  * Naive implementation of "Transaction" which just executes all operations one-by-one.
@@ -74,13 +70,13 @@ export async function commitDBTransactionSimple(
74
70
  tx: DBTransaction,
75
71
  opt?: CommonDBSaveOptions,
76
72
  ): Promise<void> {
77
- const ops = mergeDBOperations(tx.ops)
73
+ // const ops = mergeDBOperations(tx.ops)
78
74
 
79
- for await (const op of ops) {
75
+ for await (const op of tx.ops) {
80
76
  if (op.type === 'saveBatch') {
81
- await db.saveBatch(op.table, op.rows, opt)
77
+ await db.saveBatch(op.table, op.rows, { ...op.opt, ...opt })
82
78
  } else if (op.type === 'deleteByIds') {
83
- await db.deleteByIds(op.table, op.ids, opt)
79
+ await db.deleteByIds(op.table, op.ids, { ...op.opt, ...opt })
84
80
  } else {
85
81
  throw new Error(`DBOperation not supported: ${(op as any).type}`)
86
82
  }