@naturalcycles/db-lib 8.41.1 → 8.42.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 (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 +55 -3
  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 +18 -20
  13. package/dist/index.js +18 -41
  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 +2 -1
  21. package/dist/transaction/dbTransaction.util.js +56 -52
  22. package/package.json +1 -1
  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 +76 -5
  30. package/src/db.model.ts +2 -0
  31. package/src/index.ts +18 -109
  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 +32 -32
@@ -3,8 +3,9 @@ import { Readable } from 'stream';
3
3
  import { JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
4
4
  import { BaseCommonDB } from '../../base.common.db';
5
5
  import { CommonDB } from '../../common.db';
6
- import { RunQueryResult } from '../../db.model';
6
+ import { CommonDBOptions, RunQueryResult } from '../../db.model';
7
7
  import { DBQuery } from '../../query/dbQuery';
8
+ import { DBTransaction } from '../../transaction/dbTransaction';
8
9
  import { CacheDBCfg, CacheDBCreateOptions, CacheDBOptions, CacheDBSaveOptions, CacheDBStreamOptions } from './cache.db.model';
9
10
  /**
10
11
  * CommonDB implementation that proxies requests to downstream CommonDB
@@ -29,4 +30,5 @@ export declare class CacheDB extends BaseCommonDB implements CommonDB {
29
30
  runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBOptions): Promise<number>;
30
31
  streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBStreamOptions): Readable;
31
32
  deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBOptions): Promise<number>;
33
+ commitTransaction(tx: DBTransaction, opt?: CommonDBOptions): Promise<void>;
32
34
  }
@@ -195,5 +195,9 @@ class CacheDB extends base_common_db_1.BaseCommonDB {
195
195
  }
196
196
  return deletedIds;
197
197
  }
198
+ async commitTransaction(tx, opt) {
199
+ await this.cfg.downstreamDB.commitTransaction(tx, opt);
200
+ await this.cfg.cacheDB.commitTransaction(tx, opt);
201
+ }
198
202
  }
199
203
  exports.CacheDB = CacheDB;
@@ -70,13 +70,19 @@ class FileDB extends __1.BaseCommonDB {
70
70
  const rows = await this.loadFile(table);
71
71
  data[table] = (0, js_lib_1._by)(rows, r => r.id);
72
72
  }, { concurrency: 16 });
73
+ const backup = (0, js_lib_1._deepCopy)(data);
73
74
  // 2. Apply ops one by one (in order)
74
75
  tx.ops.forEach(op => {
75
76
  if (op.type === 'deleteByIds') {
76
77
  op.ids.forEach(id => delete data[op.table][id]);
77
78
  }
78
79
  else if (op.type === 'saveBatch') {
79
- op.rows.forEach(r => (data[op.table][r.id] = r));
80
+ op.rows.forEach(r => {
81
+ if (!r.id) {
82
+ throw new Error('FileDB: row has an empty id');
83
+ }
84
+ data[op.table][r.id] = r;
85
+ });
80
86
  }
81
87
  else {
82
88
  throw new Error(`DBOperation not supported: ${op.type}`);
@@ -84,15 +90,29 @@ class FileDB extends __1.BaseCommonDB {
84
90
  });
85
91
  // 3. Sort, turn it into ops
86
92
  // Not filtering empty arrays, cause it's already filtered in this.saveFiles()
87
- const ops = Object.keys(data).map(table => {
93
+ const ops = (0, js_lib_1._stringMapEntries)(data).map(([table, map]) => {
88
94
  return {
89
95
  type: 'saveBatch',
90
96
  table,
91
- rows: this.sortRows(Object.values(data[table])),
97
+ rows: this.sortRows((0, js_lib_1._stringMapValues)(map)),
92
98
  };
93
99
  });
94
100
  // 4. Save all files
95
- await this.saveFiles(ops);
101
+ try {
102
+ await this.saveFiles(ops);
103
+ }
104
+ catch (err) {
105
+ const ops = (0, js_lib_1._stringMapEntries)(backup).map(([table, map]) => {
106
+ return {
107
+ type: 'saveBatch',
108
+ table,
109
+ rows: this.sortRows((0, js_lib_1._stringMapValues)(map)),
110
+ };
111
+ });
112
+ // Rollback, ignore rollback error (if any)
113
+ await this.saveFiles(ops).catch(_ => { });
114
+ throw err;
115
+ }
96
116
  }
97
117
  async runQuery(q, _opt) {
98
118
  return {
@@ -5,7 +5,7 @@ export interface FileDBPersistencePlugin {
5
5
  ping(): Promise<void>;
6
6
  getTables(): Promise<string[]>;
7
7
  loadFile<ROW extends ObjectWithId>(table: string): Promise<ROW[]>;
8
- saveFiles(ops: DBSaveBatchOperation[]): Promise<void>;
8
+ saveFiles(ops: DBSaveBatchOperation<any>[]): Promise<void>;
9
9
  }
10
10
  export interface FileDBCfg {
11
11
  plugin: FileDBPersistencePlugin;
@@ -36,7 +36,7 @@ class InMemoryDB {
36
36
  async resetCache(_table) {
37
37
  if (_table) {
38
38
  const table = this.cfg.tablesPrefix + _table;
39
- this.cfg.logger?.log(`reset ${table}`);
39
+ this.cfg.logger.log(`reset ${table}`);
40
40
  this.data[table] = {};
41
41
  }
42
42
  else {
@@ -44,7 +44,7 @@ class InMemoryDB {
44
44
  (await this.getTables()).forEach(table => {
45
45
  this.data[table] = {};
46
46
  });
47
- this.cfg.logger?.log('reset');
47
+ this.cfg.logger.log('reset');
48
48
  }
49
49
  }
50
50
  async getTables() {
@@ -79,7 +79,7 @@ class InMemoryDB {
79
79
  (_a = this.data)[table] || (_a[table] = {});
80
80
  rows.forEach(r => {
81
81
  if (!r.id) {
82
- this.cfg.logger?.warn({ rows });
82
+ this.cfg.logger.warn({ rows });
83
83
  throw new Error(`InMemoryDB doesn't support id auto-generation in saveBatch, row without id was given`);
84
84
  }
85
85
  if (opt.saveMethod === 'insert' && this.data[table][r.id]) {
@@ -125,17 +125,26 @@ class InMemoryDB {
125
125
  return stream_1.Readable.from((0, __1.queryInMemory)(q, Object.values(this.data[table] || {})));
126
126
  }
127
127
  async commitTransaction(tx, opt) {
128
- for await (const op of tx.ops) {
129
- if (op.type === 'saveBatch') {
130
- await this.saveBatch(op.table, op.rows, opt);
131
- }
132
- else if (op.type === 'deleteByIds') {
133
- await this.deleteByIds(op.table, op.ids, opt);
134
- }
135
- else {
136
- throw new Error(`DBOperation not supported: ${op.type}`);
128
+ const backup = (0, js_lib_1._deepCopy)(this.data);
129
+ try {
130
+ for await (const op of tx.ops) {
131
+ if (op.type === 'saveBatch') {
132
+ await this.saveBatch(op.table, op.rows, { ...op.opt, ...opt });
133
+ }
134
+ else if (op.type === 'deleteByIds') {
135
+ await this.deleteByIds(op.table, op.ids, { ...op.opt, ...opt });
136
+ }
137
+ else {
138
+ throw new Error(`DBOperation not supported: ${op.type}`);
139
+ }
137
140
  }
138
141
  }
142
+ catch (err) {
143
+ // rollback
144
+ this.data = backup;
145
+ this.cfg.logger.log('InMemoryDB transaction rolled back');
146
+ throw err;
147
+ }
139
148
  }
140
149
  /**
141
150
  * Flushes all tables (all namespaces) at once.
@@ -163,7 +172,7 @@ class InMemoryDB {
163
172
  fs.createWriteStream(fname),
164
173
  ]);
165
174
  });
166
- this.cfg.logger?.log(`flushToDisk took ${(0, colors_1.dimGrey)((0, js_lib_1._since)(started))} to save ${(0, colors_1.yellow)(tables)} tables`);
175
+ this.cfg.logger.log(`flushToDisk took ${(0, colors_1.dimGrey)((0, js_lib_1._since)(started))} to save ${(0, colors_1.yellow)(tables)} tables`);
167
176
  }
168
177
  /**
169
178
  * Restores all tables (all namespaces) at once.
@@ -192,7 +201,7 @@ class InMemoryDB {
192
201
  ]);
193
202
  this.data[table] = (0, js_lib_1._by)(rows, r => r.id);
194
203
  });
195
- this.cfg.logger?.log(`restoreFromDisk took ${(0, colors_1.dimGrey)((0, js_lib_1._since)(started))} to read ${(0, colors_1.yellow)(files.length)} tables`);
204
+ this.cfg.logger.log(`restoreFromDisk took ${(0, colors_1.dimGrey)((0, js_lib_1._since)(started))} to read ${(0, colors_1.yellow)(files.length)} tables`);
196
205
  }
197
206
  }
198
207
  exports.InMemoryDB = InMemoryDB;
@@ -22,6 +22,7 @@ export declare class BaseCommonDB implements CommonDB {
22
22
  streamQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): ReadableTyped<ROW>;
23
23
  /**
24
24
  * Naive implementation.
25
+ * Doesn't support rollback on error, hence doesn't pass dbTest.
25
26
  * To be extended.
26
27
  */
27
28
  commitTransaction(tx: DBTransaction, opt?: CommonDBOptions): Promise<void>;
@@ -43,6 +43,7 @@ class BaseCommonDB {
43
43
  }
44
44
  /**
45
45
  * Naive implementation.
46
+ * Doesn't support rollback on error, hence doesn't pass dbTest.
46
47
  * To be extended.
47
48
  */
48
49
  async commitTransaction(tx, opt) {
@@ -1,6 +1,6 @@
1
1
  import { AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Saved, Unsaved } from '@naturalcycles/js-lib';
2
2
  import { AjvSchema, ObjectSchemaTyped, ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { DBModelType, RunQueryResult } from '../db.model';
3
+ import { DBDeleteByIdsOperation, DBModelType, DBOperation, DBSaveBatchOperation, RunQueryResult } from '../db.model';
4
4
  import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
5
5
  import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions } from './common.dao.model';
6
6
  /**
@@ -83,6 +83,12 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
83
83
  assignIdCreatedUpdated(obj: DBM, opt?: CommonDaoOptions): DBM;
84
84
  assignIdCreatedUpdated(obj: BM, opt?: CommonDaoOptions): Saved<BM>;
85
85
  assignIdCreatedUpdated(obj: Unsaved<BM>, opt?: CommonDaoOptions): Saved<BM>;
86
+ tx: {
87
+ save: (bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<DBM>) => Promise<DBSaveBatchOperation>;
88
+ saveBatch: (bms: Unsaved<BM>[], opt?: CommonDaoSaveOptions<DBM>) => Promise<DBSaveBatchOperation>;
89
+ deleteByIds: (ids: ID[], opt?: CommonDaoOptions) => Promise<DBDeleteByIdsOperation>;
90
+ deleteById: (id: ID, opt?: CommonDaoOptions) => Promise<DBDeleteByIdsOperation>;
91
+ };
86
92
  /**
87
93
  * Mutates with id, created, updated
88
94
  */
@@ -146,6 +152,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
146
152
  * Proxy to this.cfg.db.ping
147
153
  */
148
154
  ping(): Promise<void>;
155
+ runInTransaction(ops: Promise<DBOperation>[]): Promise<void>;
149
156
  protected logResult(started: number, op: string, res: any, table: string): void;
150
157
  protected logSaveResult(started: number, op: string, table: string): void;
151
158
  protected logStarted(op: string, table: string, force?: boolean): number;
@@ -6,6 +6,7 @@ const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
6
6
  const cnst_1 = require("../cnst");
7
7
  const db_model_1 = require("../db.model");
8
8
  const dbQuery_1 = require("../query/dbQuery");
9
+ const dbTransaction_1 = require("../transaction/dbTransaction");
9
10
  const common_dao_model_1 = require("./common.dao.model");
10
11
  /* eslint-disable no-dupe-class-members */
11
12
  const isGAE = !!process.env['GAE_INSTANCE'];
@@ -21,6 +22,48 @@ class CommonDao {
21
22
  constructor(cfg) {
22
23
  var _a;
23
24
  this.cfg = cfg;
25
+ this.tx = {
26
+ save: async (bm, opt = {}) => {
27
+ const row = (await this.save(bm, { ...opt, tx: true }));
28
+ return {
29
+ type: 'saveBatch',
30
+ table: this.cfg.table,
31
+ rows: [row],
32
+ opt: {
33
+ excludeFromIndexes: this.cfg.excludeFromIndexes,
34
+ ...opt,
35
+ },
36
+ };
37
+ },
38
+ saveBatch: async (bms, opt = {}) => {
39
+ const rows = (await this.saveBatch(bms, { ...opt, tx: true }));
40
+ return {
41
+ type: 'saveBatch',
42
+ table: this.cfg.table,
43
+ rows,
44
+ opt: {
45
+ excludeFromIndexes: this.cfg.excludeFromIndexes,
46
+ ...opt,
47
+ },
48
+ };
49
+ },
50
+ deleteByIds: async (ids, opt = {}) => {
51
+ return {
52
+ type: 'deleteByIds',
53
+ table: this.cfg.table,
54
+ ids: ids,
55
+ opt,
56
+ };
57
+ },
58
+ deleteById: async (id, opt = {}) => {
59
+ return {
60
+ type: 'deleteByIds',
61
+ table: this.cfg.table,
62
+ ids: [id],
63
+ opt,
64
+ };
65
+ },
66
+ };
24
67
  this.cfg = {
25
68
  // Default is to NOT log in AppEngine and in CI,
26
69
  // otherwise to log Operations
@@ -459,6 +502,9 @@ class CommonDao {
459
502
  const idWasGenerated = !bm.id && this.cfg.createId;
460
503
  this.assignIdCreatedUpdated(bm, opt); // mutates
461
504
  const dbm = await this.bmToDBM(bm, opt);
505
+ if (opt.tx) {
506
+ return dbm;
507
+ }
462
508
  const table = opt.table || this.cfg.table;
463
509
  if (opt.ensureUniqueId && idWasGenerated)
464
510
  await this.ensureUniqueId(table, dbm);
@@ -538,6 +584,9 @@ class CommonDao {
538
584
  const table = opt.table || this.cfg.table;
539
585
  bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt));
540
586
  const dbms = await this.bmsToDBM(bms, opt);
587
+ if (opt.tx) {
588
+ return dbms;
589
+ }
541
590
  if (opt.ensureUniqueId)
542
591
  throw new js_lib_1.AppError('ensureUniqueId is not supported in saveBatch');
543
592
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
@@ -811,9 +860,12 @@ class CommonDao {
811
860
  async ping() {
812
861
  await this.cfg.db.ping();
813
862
  }
814
- // transaction(): DBTransaction {
815
- // return this.cfg.db.transaction()
816
- // }
863
+ async runInTransaction(ops) {
864
+ if (!ops.length)
865
+ return;
866
+ const resolvedOps = await Promise.all(ops);
867
+ await this.cfg.db.commitTransaction(dbTransaction_1.DBTransaction.create(resolvedOps));
868
+ }
817
869
  logResult(started, op, res, table) {
818
870
  if (!this.cfg.logLevel)
819
871
  return;
@@ -167,6 +167,16 @@ export interface CommonDaoOptions extends CommonDBOptions {
167
167
  * @experimental
168
168
  */
169
169
  timeout?: number;
170
+ /**
171
+ * If passed - operation will not be performed immediately, but instead "added" to the transaction.
172
+ * In the end - transaction needs to be committed (by calling `commit`).
173
+ * This API is inspired by Datastore API.
174
+ *
175
+ * Only applicable to save* and delete* operations
176
+ *
177
+ * @experimental
178
+ */
179
+ tx?: boolean;
170
180
  }
171
181
  /**
172
182
  * All properties default to undefined.
@@ -45,11 +45,13 @@ export interface DBSaveBatchOperation<ROW extends ObjectWithId = AnyObjectWithId
45
45
  type: 'saveBatch';
46
46
  table: string;
47
47
  rows: ROW[];
48
+ opt?: CommonDBSaveOptions<ROW>;
48
49
  }
49
50
  export interface DBDeleteByIdsOperation {
50
51
  type: 'deleteByIds';
51
52
  table: string;
52
53
  ids: string[];
54
+ opt?: CommonDBOptions;
53
55
  }
54
56
  export declare enum DBRelation {
55
57
  ONE_TO_ONE = "ONE_TO_ONE",
package/dist/index.d.ts CHANGED
@@ -1,21 +1,19 @@
1
- import { InMemoryDB, InMemoryDBCfg } from './adapter/inmemory/inMemory.db';
2
- import { InMemoryKeyValueDB, InMemoryKeyValueDBCfg } from './adapter/inmemory/inMemoryKeyValueDB';
3
- import { queryInMemory } from './adapter/inmemory/queryInMemory';
4
- import { BaseCommonDB } from './base.common.db';
5
- import { DBLibError } from './cnst';
6
- import { CommonDB } from './common.db';
7
- import { CommonDao } from './commondao/common.dao';
8
- import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoLogLevel, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoHooks } from './commondao/common.dao.model';
9
- import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveMethod, CommonDBSaveOptions, CommonDBStreamOptions, DBDeleteByIdsOperation, DBModelType, DBOperation, DBRelation, DBSaveBatchOperation, RunQueryResult } from './db.model';
10
- import { CommonKeyValueDao, CommonKeyValueDaoCfg } from './kv/commonKeyValueDao';
11
- import { CommonKeyValueDB, KeyValueDBTuple } from './kv/commonKeyValueDB';
12
- import { createdUpdatedFields, createdUpdatedIdFields, deserializeJsonField, serializeJsonField } from './model.util';
13
- import { dbPipelineBackup, DBPipelineBackupOptions } from './pipeline/dbPipelineBackup';
14
- import { dbPipelineCopy, DBPipelineCopyOptions } from './pipeline/dbPipelineCopy';
15
- import { dbPipelineRestore, DBPipelineRestoreOptions } from './pipeline/dbPipelineRestore';
16
- import { DBQuery, DBQueryFilter, DBQueryFilterOperator, dbQueryFilterOperatorValues, DBQueryOrder, RunnableDBQuery } from './query/dbQuery';
17
- import { DBTransaction, RunnableDBTransaction } from './transaction/dbTransaction';
18
- import { commitDBTransactionSimple, mergeDBOperations } from './transaction/dbTransaction.util';
1
+ export * from './adapter/inmemory/inMemory.db';
2
+ export * from './adapter/inmemory/inMemoryKeyValueDB';
3
+ export * from './adapter/inmemory/queryInMemory';
4
+ export * from './base.common.db';
5
+ export * from './cnst';
6
+ export * from './common.db';
7
+ export * from './commondao/common.dao';
8
+ export * from './commondao/common.dao.model';
9
+ export * from './db.model';
10
+ export * from './kv/commonKeyValueDao';
11
+ export * from './kv/commonKeyValueDB';
12
+ export * from './model.util';
13
+ export * from './pipeline/dbPipelineBackup';
14
+ export * from './pipeline/dbPipelineCopy';
15
+ export * from './pipeline/dbPipelineRestore';
16
+ export * from './query/dbQuery';
17
+ export * from './transaction/dbTransaction';
18
+ export * from './transaction/dbTransaction.util';
19
19
  export * from './kv/commonKeyValueDaoMemoCache';
20
- export type { DBQueryFilterOperator, DBQueryFilter, DBQueryOrder, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoHooks, CommonDBOptions, CommonDBSaveOptions, CommonDBSaveMethod, CommonDBStreamOptions, CommonDBCreateOptions, CommonDB, RunQueryResult, CommonDaoCfg, InMemoryDBCfg, InMemoryKeyValueDBCfg, DBPipelineBackupOptions, DBPipelineRestoreOptions, DBPipelineCopyOptions, DBOperation, DBSaveBatchOperation, DBDeleteByIdsOperation, CommonKeyValueDB, CommonKeyValueDaoCfg, KeyValueDBTuple, };
21
- export { DBQuery, dbQueryFilterOperatorValues, RunnableDBQuery, CommonDaoLogLevel, DBRelation, DBModelType, CommonDao, createdUpdatedFields, createdUpdatedIdFields, InMemoryDB, InMemoryKeyValueDB, queryInMemory, serializeJsonField, deserializeJsonField, dbPipelineBackup, dbPipelineRestore, dbPipelineCopy, DBLibError, BaseCommonDB, DBTransaction, RunnableDBTransaction, mergeDBOperations, commitDBTransactionSimple, CommonKeyValueDao, };
package/dist/index.js CHANGED
@@ -1,45 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CommonKeyValueDao = exports.commitDBTransactionSimple = exports.mergeDBOperations = exports.RunnableDBTransaction = exports.DBTransaction = exports.BaseCommonDB = exports.DBLibError = exports.dbPipelineCopy = exports.dbPipelineRestore = exports.dbPipelineBackup = exports.deserializeJsonField = exports.serializeJsonField = exports.queryInMemory = exports.InMemoryKeyValueDB = exports.InMemoryDB = exports.createdUpdatedIdFields = exports.createdUpdatedFields = exports.CommonDao = exports.DBModelType = exports.DBRelation = exports.CommonDaoLogLevel = exports.RunnableDBQuery = exports.dbQueryFilterOperatorValues = exports.DBQuery = void 0;
4
3
  const tslib_1 = require("tslib");
5
- const inMemory_db_1 = require("./adapter/inmemory/inMemory.db");
6
- Object.defineProperty(exports, "InMemoryDB", { enumerable: true, get: function () { return inMemory_db_1.InMemoryDB; } });
7
- const inMemoryKeyValueDB_1 = require("./adapter/inmemory/inMemoryKeyValueDB");
8
- Object.defineProperty(exports, "InMemoryKeyValueDB", { enumerable: true, get: function () { return inMemoryKeyValueDB_1.InMemoryKeyValueDB; } });
9
- const queryInMemory_1 = require("./adapter/inmemory/queryInMemory");
10
- Object.defineProperty(exports, "queryInMemory", { enumerable: true, get: function () { return queryInMemory_1.queryInMemory; } });
11
- const base_common_db_1 = require("./base.common.db");
12
- Object.defineProperty(exports, "BaseCommonDB", { enumerable: true, get: function () { return base_common_db_1.BaseCommonDB; } });
13
- const cnst_1 = require("./cnst");
14
- Object.defineProperty(exports, "DBLibError", { enumerable: true, get: function () { return cnst_1.DBLibError; } });
15
- const common_dao_1 = require("./commondao/common.dao");
16
- Object.defineProperty(exports, "CommonDao", { enumerable: true, get: function () { return common_dao_1.CommonDao; } });
17
- const common_dao_model_1 = require("./commondao/common.dao.model");
18
- Object.defineProperty(exports, "CommonDaoLogLevel", { enumerable: true, get: function () { return common_dao_model_1.CommonDaoLogLevel; } });
19
- const db_model_1 = require("./db.model");
20
- Object.defineProperty(exports, "DBModelType", { enumerable: true, get: function () { return db_model_1.DBModelType; } });
21
- Object.defineProperty(exports, "DBRelation", { enumerable: true, get: function () { return db_model_1.DBRelation; } });
22
- const commonKeyValueDao_1 = require("./kv/commonKeyValueDao");
23
- Object.defineProperty(exports, "CommonKeyValueDao", { enumerable: true, get: function () { return commonKeyValueDao_1.CommonKeyValueDao; } });
24
- const model_util_1 = require("./model.util");
25
- Object.defineProperty(exports, "createdUpdatedFields", { enumerable: true, get: function () { return model_util_1.createdUpdatedFields; } });
26
- Object.defineProperty(exports, "createdUpdatedIdFields", { enumerable: true, get: function () { return model_util_1.createdUpdatedIdFields; } });
27
- Object.defineProperty(exports, "deserializeJsonField", { enumerable: true, get: function () { return model_util_1.deserializeJsonField; } });
28
- Object.defineProperty(exports, "serializeJsonField", { enumerable: true, get: function () { return model_util_1.serializeJsonField; } });
29
- const dbPipelineBackup_1 = require("./pipeline/dbPipelineBackup");
30
- Object.defineProperty(exports, "dbPipelineBackup", { enumerable: true, get: function () { return dbPipelineBackup_1.dbPipelineBackup; } });
31
- const dbPipelineCopy_1 = require("./pipeline/dbPipelineCopy");
32
- Object.defineProperty(exports, "dbPipelineCopy", { enumerable: true, get: function () { return dbPipelineCopy_1.dbPipelineCopy; } });
33
- const dbPipelineRestore_1 = require("./pipeline/dbPipelineRestore");
34
- Object.defineProperty(exports, "dbPipelineRestore", { enumerable: true, get: function () { return dbPipelineRestore_1.dbPipelineRestore; } });
35
- const dbQuery_1 = require("./query/dbQuery");
36
- Object.defineProperty(exports, "DBQuery", { enumerable: true, get: function () { return dbQuery_1.DBQuery; } });
37
- Object.defineProperty(exports, "dbQueryFilterOperatorValues", { enumerable: true, get: function () { return dbQuery_1.dbQueryFilterOperatorValues; } });
38
- Object.defineProperty(exports, "RunnableDBQuery", { enumerable: true, get: function () { return dbQuery_1.RunnableDBQuery; } });
39
- const dbTransaction_1 = require("./transaction/dbTransaction");
40
- Object.defineProperty(exports, "DBTransaction", { enumerable: true, get: function () { return dbTransaction_1.DBTransaction; } });
41
- Object.defineProperty(exports, "RunnableDBTransaction", { enumerable: true, get: function () { return dbTransaction_1.RunnableDBTransaction; } });
42
- const dbTransaction_util_1 = require("./transaction/dbTransaction.util");
43
- Object.defineProperty(exports, "commitDBTransactionSimple", { enumerable: true, get: function () { return dbTransaction_util_1.commitDBTransactionSimple; } });
44
- Object.defineProperty(exports, "mergeDBOperations", { enumerable: true, get: function () { return dbTransaction_util_1.mergeDBOperations; } });
4
+ tslib_1.__exportStar(require("./adapter/inmemory/inMemory.db"), exports);
5
+ tslib_1.__exportStar(require("./adapter/inmemory/inMemoryKeyValueDB"), exports);
6
+ tslib_1.__exportStar(require("./adapter/inmemory/queryInMemory"), exports);
7
+ tslib_1.__exportStar(require("./base.common.db"), exports);
8
+ tslib_1.__exportStar(require("./cnst"), exports);
9
+ tslib_1.__exportStar(require("./common.db"), exports);
10
+ tslib_1.__exportStar(require("./commondao/common.dao"), exports);
11
+ tslib_1.__exportStar(require("./commondao/common.dao.model"), exports);
12
+ tslib_1.__exportStar(require("./db.model"), exports);
13
+ tslib_1.__exportStar(require("./kv/commonKeyValueDao"), exports);
14
+ tslib_1.__exportStar(require("./kv/commonKeyValueDB"), exports);
15
+ tslib_1.__exportStar(require("./model.util"), exports);
16
+ tslib_1.__exportStar(require("./pipeline/dbPipelineBackup"), exports);
17
+ tslib_1.__exportStar(require("./pipeline/dbPipelineCopy"), exports);
18
+ tslib_1.__exportStar(require("./pipeline/dbPipelineRestore"), exports);
19
+ tslib_1.__exportStar(require("./query/dbQuery"), exports);
20
+ tslib_1.__exportStar(require("./transaction/dbTransaction"), exports);
21
+ tslib_1.__exportStar(require("./transaction/dbTransaction.util"), exports);
45
22
  tslib_1.__exportStar(require("./kv/commonKeyValueDaoMemoCache"), exports);
@@ -21,7 +21,7 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
21
21
  // tableSchemas = true,
22
22
  createTable = true, dbQueryFilter = true,
23
23
  // dbQueryFilterIn = true,
24
- dbQueryOrder = true, dbQuerySelectFields = true, streaming = true, strongConsistency = true, nullValues = true, } = features;
24
+ dbQueryOrder = true, dbQuerySelectFields = true, streaming = true, strongConsistency = true, nullValues = true, transactions = true, } = features;
25
25
  // const {
26
26
  // allowExtraPropertiesInResponse,
27
27
  // allowBooleansAsUndefined,
@@ -191,5 +191,46 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
191
191
  await db.deleteByIds(test_model_1.TEST_TABLE, rows.map(i => i.id));
192
192
  });
193
193
  }
194
+ if (transactions) {
195
+ test('transaction happy path', async () => {
196
+ // cleanup
197
+ await dao.query().deleteByQuery();
198
+ // Test that id, created, updated are created
199
+ const now = (0, js_lib_1.localTime)().unix();
200
+ await dao.runInTransaction([dao.tx.save((0, js_lib_1._omit)(item1, ['id', 'created', 'updated']))]);
201
+ const loaded = await dao.query().runQuery();
202
+ expect(loaded.length).toBe(1);
203
+ expect(loaded[0].id).toBeDefined();
204
+ expect(loaded[0].created).toBeGreaterThanOrEqual(now);
205
+ expect(loaded[0].updated).toBe(loaded[0].created);
206
+ await dao.runInTransaction([dao.tx.deleteById(loaded[0].id)]);
207
+ // saveBatch [item1, 2, 3]
208
+ // save item3 with k1: k1_mod
209
+ // delete item2
210
+ // remaining: item1, item3_with_k1_mod
211
+ await dao.runInTransaction([
212
+ dao.tx.saveBatch(items),
213
+ dao.tx.save({ ...items[2], k1: 'k1_mod' }),
214
+ dao.tx.deleteById(items[1].id),
215
+ ]);
216
+ const rows = await dao.query().runQuery();
217
+ const expected = [items[0], { ...items[2], k1: 'k1_mod' }];
218
+ (0, dbTest_1.expectMatch)(expected, rows, quirks);
219
+ });
220
+ test('transaction rollback', async () => {
221
+ await expect(dao.runInTransaction([
222
+ dao.tx.deleteById(items[2].id),
223
+ dao.tx.save({ ...items[0], k1: 5 }), // it should fail here
224
+ ])).rejects.toThrow();
225
+ const rows = await dao.query().runQuery();
226
+ const expected = [items[0], { ...items[2], k1: 'k1_mod' }];
227
+ (0, dbTest_1.expectMatch)(expected, rows, quirks);
228
+ });
229
+ if (querying) {
230
+ test('transaction cleanup', async () => {
231
+ await dao.query().deleteByQuery();
232
+ });
233
+ }
234
+ }
194
235
  }
195
236
  exports.runCommonDaoTest = runCommonDaoTest;
@@ -25,6 +25,7 @@ export interface CommonDBImplementationFeatures {
25
25
  * they will return `null` for all missing properties.
26
26
  */
27
27
  documentDB?: boolean;
28
+ transactions?: boolean;
28
29
  }
29
30
  /**
30
31
  * All options default to `false`.
@@ -4,6 +4,7 @@ exports.expectMatch = exports.runCommonDBTest = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
6
6
  const dbQuery_1 = require("../query/dbQuery");
7
+ const dbTransaction_1 = require("../transaction/dbTransaction");
7
8
  const test_model_1 = require("./test.model");
8
9
  const test_util_1 = require("./test.util");
9
10
  /**
@@ -12,7 +13,7 @@ const test_util_1 = require("./test.util");
12
13
  function runCommonDBTest(db, features = {}, quirks = {}) {
13
14
  const { querying = true, tableSchemas = true, createTable = true, dbQueryFilter = true,
14
15
  // dbQueryFilterIn = true,
15
- dbQueryOrder = true, dbQuerySelectFields = true, insert = true, update = true, streaming = true, strongConsistency = true, bufferSupport = true, nullValues = true, documentDB = true, } = features;
16
+ dbQueryOrder = true, dbQuerySelectFields = true, insert = true, update = true, streaming = true, strongConsistency = true, bufferSupport = true, nullValues = true, documentDB = true, transactions = true, } = features;
16
17
  // const {
17
18
  // allowExtraPropertiesInResponse,
18
19
  // allowBooleansAsUndefined,
@@ -95,6 +96,9 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
95
96
  test('saveBatch test items', async () => {
96
97
  await db.saveBatch(test_model_1.TEST_TABLE, items);
97
98
  });
99
+ test('saveBatch should throw on null id', async () => {
100
+ await expect(db.saveBatch(test_model_1.TEST_TABLE, [{ ...item1, id: null }])).rejects.toThrow();
101
+ });
98
102
  if (insert) {
99
103
  test('saveBatch INSERT method should throw', async () => {
100
104
  await expect(db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'insert' })).rejects.toThrow();
@@ -169,11 +173,11 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
169
173
  // getTables
170
174
  test('getTables, getTableSchema (if supported)', async () => {
171
175
  const tables = await db.getTables();
172
- console.log({ tables });
176
+ // console.log({ tables })
173
177
  if (tableSchemas) {
174
178
  await (0, js_lib_1.pMap)(tables, async (table) => {
175
179
  const schema = await db.getTableSchema(table);
176
- console.log(schema);
180
+ // console.log(schema)
177
181
  expect(schema.$id).toBe(`${table}.schema.json`);
178
182
  });
179
183
  }
@@ -201,18 +205,46 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
201
205
  await db.saveBatch(test_model_1.TEST_TABLE, [item]);
202
206
  const [loaded] = await db.getByIds(test_model_1.TEST_TABLE, [item.id]);
203
207
  const b1Loaded = loaded.b1;
204
- console.log({
205
- b11: typeof b1,
206
- b12: typeof b1Loaded,
207
- l1: b1.length,
208
- l2: b1Loaded.length,
209
- b1,
210
- b1Loaded,
211
- });
208
+ // console.log({
209
+ // b11: typeof b1,
210
+ // b12: typeof b1Loaded,
211
+ // l1: b1.length,
212
+ // l2: b1Loaded.length,
213
+ // b1,
214
+ // b1Loaded,
215
+ // })
212
216
  expect(b1Loaded).toEqual(b1);
213
217
  expect(b1Loaded.toString()).toBe(s);
214
218
  });
215
219
  }
220
+ if (transactions) {
221
+ test('transaction happy path', async () => {
222
+ // cleanup
223
+ await db.deleteByQuery(queryAll());
224
+ // saveBatch [item1, 2, 3]
225
+ // save item3 with k1: k1_mod
226
+ // delete item2
227
+ // remaining: item1, item3_with_k1_mod
228
+ const tx = dbTransaction_1.DBTransaction.create()
229
+ .saveBatch(test_model_1.TEST_TABLE, items)
230
+ .save(test_model_1.TEST_TABLE, { ...items[2], k1: 'k1_mod' })
231
+ .deleteById(test_model_1.TEST_TABLE, items[1].id);
232
+ await db.commitTransaction(tx);
233
+ const { rows } = await db.runQuery(queryAll());
234
+ const expected = [items[0], { ...items[2], k1: 'k1_mod' }];
235
+ expectMatch(expected, rows, quirks);
236
+ });
237
+ test('transaction rollback', async () => {
238
+ // It should fail on id == null
239
+ const tx = dbTransaction_1.DBTransaction.create()
240
+ .deleteById(test_model_1.TEST_TABLE, items[2].id)
241
+ .save(test_model_1.TEST_TABLE, { ...items[0], k1: 5, id: null });
242
+ await expect(db.commitTransaction(tx)).rejects.toThrow();
243
+ const { rows } = await db.runQuery(queryAll());
244
+ const expected = [items[0], { ...items[2], k1: 'k1_mod' }];
245
+ expectMatch(expected, rows, quirks);
246
+ });
247
+ }
216
248
  if (querying) {
217
249
  test('cleanup', async () => {
218
250
  // CLEAN UP
@@ -42,7 +42,7 @@ class CommonTimeSeriesDao {
42
42
  async commitTransaction(ops) {
43
43
  if (!ops.length)
44
44
  return;
45
- const tx = new __1.DBTransaction();
45
+ const tx = __1.DBTransaction.create();
46
46
  ops.forEach(op => {
47
47
  const rows = op.dataPoints.map(([ts, v]) => ({
48
48
  id: String(ts),
@@ -6,7 +6,14 @@ import type { CommonDBSaveOptions, DBOperation } from '../db.model';
6
6
  */
7
7
  export declare class DBTransaction {
8
8
  ops: DBOperation[];
9
+ protected constructor(ops?: DBOperation[]);
10
+ /**
11
+ * Convenience method.
12
+ */
13
+ static create(ops?: DBOperation[]): DBTransaction;
14
+ save<ROW extends ObjectWithId = AnyObjectWithId>(table: string, row: ROW): this;
9
15
  saveBatch<ROW extends ObjectWithId = AnyObjectWithId>(table: string, rows: ROW[]): this;
16
+ deleteById(table: string, id: string): this;
10
17
  deleteByIds(table: string, ids: string[]): this;
11
18
  }
12
19
  /**