@naturalcycles/db-lib 8.55.0 → 8.56.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.
- package/dist/adapter/file/file.db.js +1 -2
- package/dist/adapter/file/localFile.persistence.plugin.js +1 -1
- package/dist/adapter/inmemory/inMemory.db.js +2 -3
- package/dist/commondao/common.dao.d.ts +24 -14
- package/dist/commondao/common.dao.js +63 -15
- package/dist/commondao/common.dao.model.d.ts +13 -2
- package/dist/pipeline/dbPipelineBackup.js +6 -7
- package/dist/pipeline/dbPipelineCopy.js +4 -5
- package/dist/pipeline/dbPipelineRestore.js +5 -6
- package/dist/testing/daoTest.js +1 -1
- package/package.json +1 -1
- package/src/adapter/file/file.db.ts +1 -2
- package/src/adapter/file/localFile.persistence.plugin.ts +1 -1
- package/src/adapter/inmemory/inMemory.db.ts +2 -1
- package/src/commondao/common.dao.model.ts +15 -2
- package/src/commondao/common.dao.ts +94 -27
- package/src/model.util.ts +1 -1
- package/src/pipeline/dbPipelineBackup.ts +4 -1
- package/src/pipeline/dbPipelineCopy.ts +4 -1
- package/src/pipeline/dbPipelineRestore.ts +4 -1
- package/src/testing/daoTest.ts +2 -2
- package/src/testing/keyValueDaoTest.ts +1 -1
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FileDB = void 0;
|
|
4
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
5
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
|
-
const nodejs_lib_2 = require("@naturalcycles/nodejs-lib");
|
|
7
6
|
const __1 = require("../..");
|
|
8
7
|
/**
|
|
9
8
|
* Provides barebone implementation for "whole file" based CommonDB.
|
|
@@ -195,7 +194,7 @@ class FileDB extends __1.BaseCommonDB {
|
|
|
195
194
|
logFinished(started, op) {
|
|
196
195
|
if (!this.cfg.logFinished)
|
|
197
196
|
return;
|
|
198
|
-
this.cfg.logger?.log(`<< ${op} ${(0,
|
|
197
|
+
this.cfg.logger?.log(`<< ${op} ${(0, nodejs_lib_1.dimGrey)(`in ${(0, js_lib_1._since)(started)}`)}`);
|
|
199
198
|
}
|
|
200
199
|
}
|
|
201
200
|
exports.FileDB = FileDB;
|
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LocalFilePersistencePlugin = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
6
|
-
const node_stream_1 = require("node:stream");
|
|
7
6
|
const promises_1 = tslib_1.__importDefault(require("node:fs/promises"));
|
|
7
|
+
const node_stream_1 = require("node:stream");
|
|
8
8
|
const node_zlib_1 = require("node:zlib");
|
|
9
9
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
10
10
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
@@ -8,7 +8,6 @@ const node_stream_1 = require("node:stream");
|
|
|
8
8
|
const node_zlib_1 = require("node:zlib");
|
|
9
9
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
10
10
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
11
|
-
const nodejs_lib_2 = require("@naturalcycles/nodejs-lib");
|
|
12
11
|
const __1 = require("../..");
|
|
13
12
|
const dbQuery_1 = require("../../query/dbQuery");
|
|
14
13
|
class InMemoryDB {
|
|
@@ -185,7 +184,7 @@ class InMemoryDB {
|
|
|
185
184
|
node_fs_1.default.createWriteStream(fname),
|
|
186
185
|
]);
|
|
187
186
|
});
|
|
188
|
-
this.cfg.logger.log(`flushToDisk took ${(0,
|
|
187
|
+
this.cfg.logger.log(`flushToDisk took ${(0, nodejs_lib_1.dimGrey)((0, js_lib_1._since)(started))} to save ${(0, nodejs_lib_1.yellow)(tables)} tables`);
|
|
189
188
|
}
|
|
190
189
|
/**
|
|
191
190
|
* Restores all tables (all namespaces) at once.
|
|
@@ -212,7 +211,7 @@ class InMemoryDB {
|
|
|
212
211
|
]);
|
|
213
212
|
this.data[table] = (0, js_lib_1._by)(rows, r => r.id);
|
|
214
213
|
});
|
|
215
|
-
this.cfg.logger.log(`restoreFromDisk took ${(0,
|
|
214
|
+
this.cfg.logger.log(`restoreFromDisk took ${(0, nodejs_lib_1.dimGrey)((0, js_lib_1._since)(started))} to read ${(0, nodejs_lib_1.yellow)(files.length)} tables`);
|
|
216
215
|
}
|
|
217
216
|
}
|
|
218
217
|
exports.InMemoryDB = InMemoryDB;
|
|
@@ -4,7 +4,7 @@ import { AnyObject, AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectW
|
|
|
4
4
|
import { AjvSchema, ObjectSchema, ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
5
5
|
import { DBDeleteByIdsOperation, DBModelType, DBOperation, DBPatch, DBSaveBatchOperation, RunQueryResult } from '../db.model';
|
|
6
6
|
import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
|
|
7
|
-
import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model';
|
|
7
|
+
import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model';
|
|
8
8
|
/**
|
|
9
9
|
* Lowest common denominator API between supported Databases.
|
|
10
10
|
*
|
|
@@ -86,28 +86,38 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
86
86
|
assignIdCreatedUpdated(obj: BM, opt?: CommonDaoOptions): Saved<BM>;
|
|
87
87
|
assignIdCreatedUpdated(obj: Unsaved<BM>, opt?: CommonDaoOptions): Saved<BM>;
|
|
88
88
|
tx: {
|
|
89
|
-
save: (bm: Unsaved<BM>, opt?:
|
|
90
|
-
saveBatch: (bms: Unsaved<BM>[], opt?:
|
|
89
|
+
save: (bm: Unsaved<BM>, opt?: CommonDaoSaveBatchOptions<DBM>) => Promise<DBSaveBatchOperation | undefined>;
|
|
90
|
+
saveBatch: (bms: Unsaved<BM>[], opt?: CommonDaoSaveBatchOptions<DBM>) => Promise<DBSaveBatchOperation | undefined>;
|
|
91
91
|
deleteByIds: (ids: ID[], opt?: CommonDaoOptions) => Promise<DBDeleteByIdsOperation | undefined>;
|
|
92
92
|
deleteById: (id: ID | null | undefined, opt?: CommonDaoOptions) => Promise<DBDeleteByIdsOperation | undefined>;
|
|
93
93
|
};
|
|
94
94
|
/**
|
|
95
95
|
* Mutates with id, created, updated
|
|
96
96
|
*/
|
|
97
|
-
save(bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>>;
|
|
97
|
+
save(bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<Saved<BM>>;
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* Saves (as fast as possible) with the Patch applied.
|
|
99
|
+
* 1. Applies the patch
|
|
100
|
+
* 2. If object is the same after patching - skips saving it
|
|
101
|
+
* 3. Otherwise - saves the patched object and returns it
|
|
103
102
|
*
|
|
104
|
-
*
|
|
103
|
+
* Similar to `save` with skipIfEquals.
|
|
104
|
+
* Similar to `patch`, but doesn't load the object from the Database.
|
|
105
|
+
*/
|
|
106
|
+
savePatch(bm: Saved<BM>, patch: Partial<BM>, opt: CommonDaoSaveBatchOptions<DBM>): Promise<Saved<BM>>;
|
|
107
|
+
/**
|
|
108
|
+
* Convenience method to replace 3 operations (loading+patching+saving) with one:
|
|
109
|
+
*
|
|
110
|
+
* 1. Loads the row by id.
|
|
111
|
+
* 1.1 Creates the row (via this.create()) if it doesn't exist
|
|
112
|
+
* (this will cause a validation error if Patch has not enough data for the row to be valid).
|
|
113
|
+
* 2. Applies the patch on top of loaded data.
|
|
114
|
+
* 3. Saves (as fast as possible since the read) with the Patch applied.
|
|
105
115
|
*/
|
|
106
|
-
patch(id: ID, patch: Partial<BM>, opt?:
|
|
107
|
-
patchAsDBM(id: ID, patch: Partial<DBM>, opt?:
|
|
108
|
-
saveAsDBM(dbm: DBM, opt?:
|
|
109
|
-
saveBatch(bms: Unsaved<BM>[], opt?:
|
|
110
|
-
saveBatchAsDBM(dbms: DBM[], opt?:
|
|
116
|
+
patch(id: ID, patch: Partial<BM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<Saved<BM>>;
|
|
117
|
+
patchAsDBM(id: ID, patch: Partial<DBM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<DBM>;
|
|
118
|
+
saveAsDBM(dbm: DBM, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<DBM>;
|
|
119
|
+
saveBatch(bms: Unsaved<BM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<Saved<BM>[]>;
|
|
120
|
+
saveBatchAsDBM(dbms: DBM[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<DBM[]>;
|
|
111
121
|
/**
|
|
112
122
|
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
113
123
|
* (of size opt.batchSize, which defaults to 500),
|
|
@@ -555,6 +555,10 @@ class CommonDao {
|
|
|
555
555
|
*/
|
|
556
556
|
async save(bm, opt = {}) {
|
|
557
557
|
this.requireWriteAccess();
|
|
558
|
+
if (opt.skipIfEquals && (0, js_lib_1._deepJsonEquals)(bm, opt.skipIfEquals)) {
|
|
559
|
+
// Skipping the save operation
|
|
560
|
+
return bm;
|
|
561
|
+
}
|
|
558
562
|
const idWasGenerated = !bm.id && this.cfg.createId;
|
|
559
563
|
this.assignIdCreatedUpdated(bm, opt); // mutates
|
|
560
564
|
let dbm = await this.bmToDBM(bm, opt);
|
|
@@ -589,26 +593,70 @@ class CommonDao {
|
|
|
589
593
|
return bm;
|
|
590
594
|
}
|
|
591
595
|
/**
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
* Saves (as fast as possible) with the Patch applied.
|
|
596
|
+
* 1. Applies the patch
|
|
597
|
+
* 2. If object is the same after patching - skips saving it
|
|
598
|
+
* 3. Otherwise - saves the patched object and returns it
|
|
596
599
|
*
|
|
597
|
-
*
|
|
600
|
+
* Similar to `save` with skipIfEquals.
|
|
601
|
+
* Similar to `patch`, but doesn't load the object from the Database.
|
|
598
602
|
*/
|
|
599
|
-
async
|
|
600
|
-
|
|
601
|
-
...
|
|
603
|
+
async savePatch(bm, patch, opt) {
|
|
604
|
+
const patched = {
|
|
605
|
+
...bm,
|
|
602
606
|
...patch,
|
|
603
|
-
}
|
|
607
|
+
};
|
|
608
|
+
if ((0, js_lib_1._deepJsonEquals)(bm, patched)) {
|
|
609
|
+
// Skipping the save operation, as data is the same
|
|
610
|
+
return bm;
|
|
611
|
+
}
|
|
612
|
+
// Actually apply the patch by mutating the original object (by design)
|
|
613
|
+
Object.assign(bm, patch);
|
|
614
|
+
return await this.save(bm, opt);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Convenience method to replace 3 operations (loading+patching+saving) with one:
|
|
618
|
+
*
|
|
619
|
+
* 1. Loads the row by id.
|
|
620
|
+
* 1.1 Creates the row (via this.create()) if it doesn't exist
|
|
621
|
+
* (this will cause a validation error if Patch has not enough data for the row to be valid).
|
|
622
|
+
* 2. Applies the patch on top of loaded data.
|
|
623
|
+
* 3. Saves (as fast as possible since the read) with the Patch applied.
|
|
624
|
+
*/
|
|
625
|
+
async patch(id, patch, opt = {}) {
|
|
626
|
+
const bm = await this.getById(id, opt);
|
|
627
|
+
let patched;
|
|
628
|
+
if (bm) {
|
|
629
|
+
patched = {
|
|
630
|
+
...bm,
|
|
631
|
+
...patch,
|
|
632
|
+
};
|
|
633
|
+
if ((0, js_lib_1._deepJsonEquals)(bm, patched)) {
|
|
634
|
+
// Skipping the save operation, as data is the same
|
|
635
|
+
return bm;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
patched = this.create({ ...patch, id }, opt);
|
|
640
|
+
}
|
|
641
|
+
return await this.save(patched, opt);
|
|
604
642
|
}
|
|
605
643
|
async patchAsDBM(id, patch, opt = {}) {
|
|
606
|
-
const dbm =
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
644
|
+
const dbm = await this.getByIdAsDBM(id, opt);
|
|
645
|
+
let patched;
|
|
646
|
+
if (dbm) {
|
|
647
|
+
patched = {
|
|
648
|
+
...dbm,
|
|
649
|
+
...patch,
|
|
650
|
+
};
|
|
651
|
+
if ((0, js_lib_1._deepJsonEquals)(dbm, patched)) {
|
|
652
|
+
// Skipping the save operation, as data is the same
|
|
653
|
+
return dbm;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
patched = this.create({ ...patch, id }, opt);
|
|
658
|
+
}
|
|
659
|
+
return await this.saveAsDBM(patched, opt);
|
|
612
660
|
}
|
|
613
661
|
async saveAsDBM(dbm, opt = {}) {
|
|
614
662
|
this.requireWriteAccess();
|
|
@@ -237,10 +237,21 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
237
237
|
*/
|
|
238
238
|
tx?: boolean;
|
|
239
239
|
}
|
|
240
|
+
export interface CommonDaoSaveOptions<BM extends Partial<ObjectWithId>, DBM extends ObjectWithId> extends CommonDaoSaveBatchOptions<DBM> {
|
|
241
|
+
/**
|
|
242
|
+
* If provided - a check will be made.
|
|
243
|
+
* If the object for saving equals to the object passed to `skipIfEquals` - save operation will be skipped.
|
|
244
|
+
*
|
|
245
|
+
* Equality is checked with _deepJsonEquals (aka "deep equals after JSON.stringify/parse", which removes keys with undefined values).
|
|
246
|
+
*
|
|
247
|
+
* It's supposed to be used to prevent "unnecessary saves", when data is not changed.
|
|
248
|
+
*/
|
|
249
|
+
skipIfEquals?: BM;
|
|
250
|
+
}
|
|
240
251
|
/**
|
|
241
252
|
* All properties default to undefined.
|
|
242
253
|
*/
|
|
243
|
-
export interface
|
|
254
|
+
export interface CommonDaoSaveBatchOptions<DBM extends ObjectWithId> extends CommonDaoOptions, CommonDBSaveOptions<DBM> {
|
|
244
255
|
/**
|
|
245
256
|
* @default false
|
|
246
257
|
*
|
|
@@ -254,7 +265,7 @@ export interface CommonDaoSaveOptions<DBM extends ObjectWithId> extends CommonDa
|
|
|
254
265
|
}
|
|
255
266
|
export interface CommonDaoStreamDeleteOptions<DBM extends ObjectWithId> extends CommonDaoStreamOptions<DBM> {
|
|
256
267
|
}
|
|
257
|
-
export interface CommonDaoStreamSaveOptions<DBM extends ObjectWithId> extends
|
|
268
|
+
export interface CommonDaoStreamSaveOptions<DBM extends ObjectWithId> extends CommonDaoSaveBatchOptions<DBM>, CommonDaoStreamOptions<DBM> {
|
|
258
269
|
}
|
|
259
270
|
export interface CommonDaoStreamForEachOptions<IN> extends CommonDaoStreamOptions<IN>, TransformMapOptions<IN, any> {
|
|
260
271
|
}
|
|
@@ -7,7 +7,6 @@ const promises_1 = tslib_1.__importDefault(require("node:fs/promises"));
|
|
|
7
7
|
const node_zlib_1 = require("node:zlib");
|
|
8
8
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
9
9
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
10
|
-
const nodejs_lib_2 = require("@naturalcycles/nodejs-lib");
|
|
11
10
|
const index_1 = require("../index");
|
|
12
11
|
/**
|
|
13
12
|
* Pipeline from input stream(s) to a NDJSON file (optionally gzipped).
|
|
@@ -23,11 +22,11 @@ async function dbPipelineBackup(opt) {
|
|
|
23
22
|
const strict = errorMode !== js_lib_1.ErrorMode.SUPPRESS;
|
|
24
23
|
const gzip = opt.gzip !== false; // default to true
|
|
25
24
|
let { tables } = opt;
|
|
26
|
-
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0,
|
|
27
|
-
console.log(`>> ${(0,
|
|
25
|
+
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0, nodejs_lib_1.grey)((0, js_lib_1.localTime)(sinceUpdated).toPretty()) : '';
|
|
26
|
+
console.log(`>> ${(0, nodejs_lib_1.dimWhite)('dbPipelineBackup')} started in ${(0, nodejs_lib_1.grey)(outputDirPath)}...${sinceUpdatedStr}`);
|
|
28
27
|
(0, nodejs_lib_1._ensureDirSync)(outputDirPath);
|
|
29
28
|
tables ||= await db.getTables();
|
|
30
|
-
console.log(`${(0,
|
|
29
|
+
console.log(`${(0, nodejs_lib_1.yellow)(tables.length)} ${(0, nodejs_lib_1.boldWhite)('table(s)')}:\n` + tables.join('\n'));
|
|
31
30
|
const statsPerTable = {};
|
|
32
31
|
await (0, js_lib_1.pMap)(tables, async (table) => {
|
|
33
32
|
let q = index_1.DBQuery.create(table).limit(limit);
|
|
@@ -42,11 +41,11 @@ async function dbPipelineBackup(opt) {
|
|
|
42
41
|
const started = Date.now();
|
|
43
42
|
let rows = 0;
|
|
44
43
|
(0, nodejs_lib_1._ensureFileSync)(filePath);
|
|
45
|
-
console.log(`>> ${(0,
|
|
44
|
+
console.log(`>> ${(0, nodejs_lib_1.grey)(filePath)} started...`);
|
|
46
45
|
if (emitSchemaFromDB) {
|
|
47
46
|
const schema = await db.getTableSchema(table);
|
|
48
47
|
await (0, nodejs_lib_1._writeJson)(schemaFilePath, schema, { spaces: 2 });
|
|
49
|
-
console.log(`>> ${(0,
|
|
48
|
+
console.log(`>> ${(0, nodejs_lib_1.grey)(schemaFilePath)} saved (generated from DB)`);
|
|
50
49
|
}
|
|
51
50
|
await (0, nodejs_lib_1._pipeline)([
|
|
52
51
|
db.streamQuery(q),
|
|
@@ -74,7 +73,7 @@ async function dbPipelineBackup(opt) {
|
|
|
74
73
|
rows,
|
|
75
74
|
sizeBytes,
|
|
76
75
|
});
|
|
77
|
-
console.log(`>> ${(0,
|
|
76
|
+
console.log(`>> ${(0, nodejs_lib_1.grey)(filePath)}\n` + stats.toPretty());
|
|
78
77
|
statsPerTable[table] = stats;
|
|
79
78
|
}, { concurrency, errorMode });
|
|
80
79
|
const statsTotal = nodejs_lib_1.NDJsonStats.createCombined(Object.values(statsPerTable));
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.dbPipelineCopy = void 0;
|
|
4
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
5
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
|
-
const nodejs_lib_2 = require("@naturalcycles/nodejs-lib");
|
|
7
6
|
const dbQuery_1 = require("../query/dbQuery");
|
|
8
7
|
/**
|
|
9
8
|
* Pipeline from input stream(s) to CommonDB .saveBatch().
|
|
@@ -14,10 +13,10 @@ const dbQuery_1 = require("../query/dbQuery");
|
|
|
14
13
|
async function dbPipelineCopy(opt) {
|
|
15
14
|
const { batchSize = 100, dbInput, dbOutput, concurrency = 16, limit = 0, sinceUpdated, mapperPerTable = {}, saveOptionsPerTable = {}, transformMapOptions, errorMode = js_lib_1.ErrorMode.SUPPRESS, } = opt;
|
|
16
15
|
let { tables } = opt;
|
|
17
|
-
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0,
|
|
18
|
-
console.log(`>> ${(0,
|
|
16
|
+
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0, nodejs_lib_1.grey)((0, js_lib_1.localTime)(sinceUpdated).toPretty()) : '';
|
|
17
|
+
console.log(`>> ${(0, nodejs_lib_1.dimWhite)('dbPipelineCopy')} started...${sinceUpdatedStr}`);
|
|
19
18
|
tables ||= await dbInput.getTables();
|
|
20
|
-
console.log(`${(0,
|
|
19
|
+
console.log(`${(0, nodejs_lib_1.yellow)(tables.length)} ${(0, nodejs_lib_1.boldWhite)('table(s)')}:\n` + tables.join('\n'));
|
|
21
20
|
const statsPerTable = {};
|
|
22
21
|
await (0, js_lib_1.pMap)(tables, async (table) => {
|
|
23
22
|
let q = dbQuery_1.DBQuery.create(table).limit(limit);
|
|
@@ -53,7 +52,7 @@ async function dbPipelineCopy(opt) {
|
|
|
53
52
|
rows,
|
|
54
53
|
sizeBytes: 0, // n/a
|
|
55
54
|
});
|
|
56
|
-
console.log(`>> ${(0,
|
|
55
|
+
console.log(`>> ${(0, nodejs_lib_1.grey)(table)}\n` + stats.toPretty());
|
|
57
56
|
statsPerTable[table] = stats;
|
|
58
57
|
}, { concurrency, errorMode });
|
|
59
58
|
const statsTotal = nodejs_lib_1.NDJsonStats.createCombined(Object.values(statsPerTable));
|
|
@@ -6,7 +6,6 @@ const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
|
6
6
|
const node_zlib_1 = require("node:zlib");
|
|
7
7
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
8
8
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
9
|
-
const nodejs_lib_2 = require("@naturalcycles/nodejs-lib");
|
|
10
9
|
/**
|
|
11
10
|
* Pipeline from NDJSON files in a folder (optionally gzipped) to CommonDB.
|
|
12
11
|
* Allows to define a mapper and a predicate to map/filter objects between input and output.
|
|
@@ -18,8 +17,8 @@ async function dbPipelineRestore(opt) {
|
|
|
18
17
|
const { db, concurrency = 16, batchSize = 100, limit, sinceUpdated, inputDirPath, mapperPerTable = {}, saveOptionsPerTable = {}, transformMapOptions, errorMode = js_lib_1.ErrorMode.SUPPRESS, recreateTables = false, } = opt;
|
|
19
18
|
const strict = errorMode !== js_lib_1.ErrorMode.SUPPRESS;
|
|
20
19
|
const onlyTables = opt.tables && new Set(opt.tables);
|
|
21
|
-
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0,
|
|
22
|
-
console.log(`>> ${(0,
|
|
20
|
+
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0, nodejs_lib_1.grey)((0, js_lib_1.localTime)(sinceUpdated).toPretty()) : '';
|
|
21
|
+
console.log(`>> ${(0, nodejs_lib_1.dimWhite)('dbPipelineRestore')} started in ${(0, nodejs_lib_1.grey)(inputDirPath)}...${sinceUpdatedStr}`);
|
|
23
22
|
(0, nodejs_lib_1._ensureDirSync)(inputDirPath);
|
|
24
23
|
const tablesToGzip = new Set();
|
|
25
24
|
const sizeByTable = {};
|
|
@@ -46,7 +45,7 @@ async function dbPipelineRestore(opt) {
|
|
|
46
45
|
sizeByTable[table] = node_fs_1.default.statSync(`${inputDirPath}/${f}`).size;
|
|
47
46
|
});
|
|
48
47
|
const sizeStrByTable = (0, js_lib_1._mapValues)(sizeByTable, (_k, b) => (0, js_lib_1._hb)(b));
|
|
49
|
-
console.log(`${(0,
|
|
48
|
+
console.log(`${(0, nodejs_lib_1.yellow)(tables.length)} ${(0, nodejs_lib_1.boldWhite)('table(s)')}:\n`, sizeStrByTable);
|
|
50
49
|
// const schemaByTable: Record<string, CommonSchema> = {}
|
|
51
50
|
if (recreateTables) {
|
|
52
51
|
await (0, js_lib_1.pMap)(tables, async (table) => {
|
|
@@ -66,7 +65,7 @@ async function dbPipelineRestore(opt) {
|
|
|
66
65
|
const started = Date.now();
|
|
67
66
|
let rows = 0;
|
|
68
67
|
const sizeBytes = sizeByTable[table];
|
|
69
|
-
console.log(`<< ${(0,
|
|
68
|
+
console.log(`<< ${(0, nodejs_lib_1.grey)(filePath)} ${(0, nodejs_lib_1.dimWhite)((0, js_lib_1._hb)(sizeBytes))} started...`);
|
|
70
69
|
await (0, nodejs_lib_1._pipeline)([
|
|
71
70
|
node_fs_1.default.createReadStream(filePath),
|
|
72
71
|
...(gzip ? [(0, node_zlib_1.createUnzip)()] : []),
|
|
@@ -98,7 +97,7 @@ async function dbPipelineRestore(opt) {
|
|
|
98
97
|
rows,
|
|
99
98
|
sizeBytes,
|
|
100
99
|
});
|
|
101
|
-
console.log(`<< ${(0,
|
|
100
|
+
console.log(`<< ${(0, nodejs_lib_1.grey)(filePath)}\n` + stats.toPretty());
|
|
102
101
|
statsPerTable[table] = stats;
|
|
103
102
|
}, { concurrency, errorMode });
|
|
104
103
|
const statsTotal = nodejs_lib_1.NDJsonStats.createCombined(Object.values(statsPerTable));
|
package/dist/testing/daoTest.js
CHANGED
|
@@ -205,7 +205,7 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
|
|
|
205
205
|
// cleanup
|
|
206
206
|
await dao.query().deleteByQuery();
|
|
207
207
|
// Test that id, created, updated are created
|
|
208
|
-
const now = (0, js_lib_1.
|
|
208
|
+
const now = (0, js_lib_1.localTimeNow)().unix();
|
|
209
209
|
await dao.runInTransaction([dao.tx.save((0, js_lib_1._omit)(item1, ['id', 'created', 'updated']))]);
|
|
210
210
|
const loaded = await dao.query().runQuery();
|
|
211
211
|
expect(loaded.length).toBe(1);
|
package/package.json
CHANGED
|
@@ -17,8 +17,7 @@ import {
|
|
|
17
17
|
_stringMapEntries,
|
|
18
18
|
Saved,
|
|
19
19
|
} from '@naturalcycles/js-lib'
|
|
20
|
-
import { readableCreate, ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
21
|
-
import { dimGrey } from '@naturalcycles/nodejs-lib'
|
|
20
|
+
import { readableCreate, ReadableTyped, dimGrey } from '@naturalcycles/nodejs-lib'
|
|
22
21
|
import { BaseCommonDB, DBSaveBatchOperation, queryInMemory } from '../..'
|
|
23
22
|
import { CommonDB } from '../../common.db'
|
|
24
23
|
import {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
|
-
import { Readable } from 'node:stream'
|
|
3
2
|
import fsp from 'node:fs/promises'
|
|
3
|
+
import { Readable } from 'node:stream'
|
|
4
4
|
import { createGzip, createUnzip } from 'node:zlib'
|
|
5
5
|
import { pMap, ObjectWithId } from '@naturalcycles/js-lib'
|
|
6
6
|
import {
|
|
@@ -27,8 +27,9 @@ import {
|
|
|
27
27
|
_pipeline,
|
|
28
28
|
_emptyDir,
|
|
29
29
|
_ensureDir,
|
|
30
|
+
dimGrey,
|
|
31
|
+
yellow,
|
|
30
32
|
} from '@naturalcycles/nodejs-lib'
|
|
31
|
-
import { dimGrey, yellow } from '@naturalcycles/nodejs-lib'
|
|
32
33
|
import { CommonDB, DBIncrement, DBPatch, DBTransaction, queryInMemory } from '../..'
|
|
33
34
|
import {
|
|
34
35
|
CommonDBCreateOptions,
|
|
@@ -298,10 +298,23 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
298
298
|
tx?: boolean
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
export interface CommonDaoSaveOptions<BM extends Partial<ObjectWithId>, DBM extends ObjectWithId>
|
|
302
|
+
extends CommonDaoSaveBatchOptions<DBM> {
|
|
303
|
+
/**
|
|
304
|
+
* If provided - a check will be made.
|
|
305
|
+
* If the object for saving equals to the object passed to `skipIfEquals` - save operation will be skipped.
|
|
306
|
+
*
|
|
307
|
+
* Equality is checked with _deepJsonEquals (aka "deep equals after JSON.stringify/parse", which removes keys with undefined values).
|
|
308
|
+
*
|
|
309
|
+
* It's supposed to be used to prevent "unnecessary saves", when data is not changed.
|
|
310
|
+
*/
|
|
311
|
+
skipIfEquals?: BM
|
|
312
|
+
}
|
|
313
|
+
|
|
301
314
|
/**
|
|
302
315
|
* All properties default to undefined.
|
|
303
316
|
*/
|
|
304
|
-
export interface
|
|
317
|
+
export interface CommonDaoSaveBatchOptions<DBM extends ObjectWithId>
|
|
305
318
|
extends CommonDaoOptions,
|
|
306
319
|
CommonDBSaveOptions<DBM> {
|
|
307
320
|
/**
|
|
@@ -320,7 +333,7 @@ export interface CommonDaoStreamDeleteOptions<DBM extends ObjectWithId>
|
|
|
320
333
|
extends CommonDaoStreamOptions<DBM> {}
|
|
321
334
|
|
|
322
335
|
export interface CommonDaoStreamSaveOptions<DBM extends ObjectWithId>
|
|
323
|
-
extends
|
|
336
|
+
extends CommonDaoSaveBatchOptions<DBM>,
|
|
324
337
|
CommonDaoStreamOptions<DBM> {}
|
|
325
338
|
|
|
326
339
|
export interface CommonDaoStreamForEachOptions<IN>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Transform } from 'node:stream'
|
|
2
2
|
import {
|
|
3
3
|
_assert,
|
|
4
|
+
_deepJsonEquals,
|
|
4
5
|
_filterNullishValues,
|
|
5
6
|
_filterUndefinedValues,
|
|
6
7
|
_isTruthy,
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
CommonDaoHooks,
|
|
58
59
|
CommonDaoLogLevel,
|
|
59
60
|
CommonDaoOptions,
|
|
61
|
+
CommonDaoSaveBatchOptions,
|
|
60
62
|
CommonDaoSaveOptions,
|
|
61
63
|
CommonDaoStreamDeleteOptions,
|
|
62
64
|
CommonDaoStreamForEachOptions,
|
|
@@ -697,7 +699,7 @@ export class CommonDao<
|
|
|
697
699
|
tx = {
|
|
698
700
|
save: async (
|
|
699
701
|
bm: Unsaved<BM>,
|
|
700
|
-
opt:
|
|
702
|
+
opt: CommonDaoSaveBatchOptions<DBM> = {},
|
|
701
703
|
): Promise<DBSaveBatchOperation | undefined> => {
|
|
702
704
|
// .save actually returns DBM (not BM) when it detects `opt.tx === true`
|
|
703
705
|
const row: DBM | null = (await this.save(bm, { ...opt, tx: true })) as any
|
|
@@ -715,7 +717,7 @@ export class CommonDao<
|
|
|
715
717
|
},
|
|
716
718
|
saveBatch: async (
|
|
717
719
|
bms: Unsaved<BM>[],
|
|
718
|
-
opt:
|
|
720
|
+
opt: CommonDaoSaveBatchOptions<DBM> = {},
|
|
719
721
|
): Promise<DBSaveBatchOperation | undefined> => {
|
|
720
722
|
const rows: DBM[] = (await this.saveBatch(bms, { ...opt, tx: true })) as any
|
|
721
723
|
if (!rows.length) return
|
|
@@ -760,8 +762,14 @@ export class CommonDao<
|
|
|
760
762
|
/**
|
|
761
763
|
* Mutates with id, created, updated
|
|
762
764
|
*/
|
|
763
|
-
async save(bm: Unsaved<BM>, opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>> {
|
|
765
|
+
async save(bm: Unsaved<BM>, opt: CommonDaoSaveOptions<BM, DBM> = {}): Promise<Saved<BM>> {
|
|
764
766
|
this.requireWriteAccess()
|
|
767
|
+
|
|
768
|
+
if (opt.skipIfEquals && _deepJsonEquals(bm, opt.skipIfEquals)) {
|
|
769
|
+
// Skipping the save operation
|
|
770
|
+
return bm as Saved<BM>
|
|
771
|
+
}
|
|
772
|
+
|
|
765
773
|
const idWasGenerated = !bm.id && this.cfg.createId
|
|
766
774
|
this.assignIdCreatedUpdated(bm, opt) // mutates
|
|
767
775
|
let dbm = await this.bmToDBM(bm as BM, opt)
|
|
@@ -801,38 +809,94 @@ export class CommonDao<
|
|
|
801
809
|
}
|
|
802
810
|
|
|
803
811
|
/**
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
*
|
|
807
|
-
*
|
|
812
|
+
* 1. Applies the patch
|
|
813
|
+
* 2. If object is the same after patching - skips saving it
|
|
814
|
+
* 3. Otherwise - saves the patched object and returns it
|
|
815
|
+
*
|
|
816
|
+
* Similar to `save` with skipIfEquals.
|
|
817
|
+
* Similar to `patch`, but doesn't load the object from the Database.
|
|
818
|
+
*/
|
|
819
|
+
async savePatch(
|
|
820
|
+
bm: Saved<BM>,
|
|
821
|
+
patch: Partial<BM>,
|
|
822
|
+
opt: CommonDaoSaveBatchOptions<DBM>,
|
|
823
|
+
): Promise<Saved<BM>> {
|
|
824
|
+
const patched: Saved<BM> = {
|
|
825
|
+
...bm,
|
|
826
|
+
...patch,
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (_deepJsonEquals(bm, patched)) {
|
|
830
|
+
// Skipping the save operation, as data is the same
|
|
831
|
+
return bm
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Actually apply the patch by mutating the original object (by design)
|
|
835
|
+
Object.assign(bm, patch)
|
|
836
|
+
|
|
837
|
+
return await this.save(bm, opt)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Convenience method to replace 3 operations (loading+patching+saving) with one:
|
|
808
842
|
*
|
|
809
|
-
*
|
|
843
|
+
* 1. Loads the row by id.
|
|
844
|
+
* 1.1 Creates the row (via this.create()) if it doesn't exist
|
|
845
|
+
* (this will cause a validation error if Patch has not enough data for the row to be valid).
|
|
846
|
+
* 2. Applies the patch on top of loaded data.
|
|
847
|
+
* 3. Saves (as fast as possible since the read) with the Patch applied.
|
|
810
848
|
*/
|
|
811
|
-
async patch(
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
849
|
+
async patch(
|
|
850
|
+
id: ID,
|
|
851
|
+
patch: Partial<BM>,
|
|
852
|
+
opt: CommonDaoSaveBatchOptions<DBM> = {},
|
|
853
|
+
): Promise<Saved<BM>> {
|
|
854
|
+
const bm = await this.getById(id, opt)
|
|
855
|
+
let patched: Saved<BM>
|
|
856
|
+
|
|
857
|
+
if (bm) {
|
|
858
|
+
patched = {
|
|
859
|
+
...bm,
|
|
815
860
|
...patch,
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (_deepJsonEquals(bm, patched)) {
|
|
864
|
+
// Skipping the save operation, as data is the same
|
|
865
|
+
return bm
|
|
866
|
+
}
|
|
867
|
+
} else {
|
|
868
|
+
patched = this.create({ ...patch, id }, opt)
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return await this.save(patched, opt)
|
|
819
872
|
}
|
|
820
873
|
|
|
821
|
-
async patchAsDBM(
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
874
|
+
async patchAsDBM(
|
|
875
|
+
id: ID,
|
|
876
|
+
patch: Partial<DBM>,
|
|
877
|
+
opt: CommonDaoSaveBatchOptions<DBM> = {},
|
|
878
|
+
): Promise<DBM> {
|
|
879
|
+
const dbm = await this.getByIdAsDBM(id, opt)
|
|
880
|
+
let patched: DBM
|
|
825
881
|
|
|
826
|
-
|
|
827
|
-
{
|
|
882
|
+
if (dbm) {
|
|
883
|
+
patched = {
|
|
828
884
|
...dbm,
|
|
829
885
|
...patch,
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (_deepJsonEquals(dbm, patched)) {
|
|
889
|
+
// Skipping the save operation, as data is the same
|
|
890
|
+
return dbm
|
|
891
|
+
}
|
|
892
|
+
} else {
|
|
893
|
+
patched = this.create({ ...patch, id } as Partial<BM>, opt) as any as DBM
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return await this.saveAsDBM(patched, opt)
|
|
833
897
|
}
|
|
834
898
|
|
|
835
|
-
async saveAsDBM(dbm: DBM, opt:
|
|
899
|
+
async saveAsDBM(dbm: DBM, opt: CommonDaoSaveBatchOptions<DBM> = {}): Promise<DBM> {
|
|
836
900
|
this.requireWriteAccess()
|
|
837
901
|
const table = opt.table || this.cfg.table
|
|
838
902
|
|
|
@@ -872,7 +936,10 @@ export class CommonDao<
|
|
|
872
936
|
return row
|
|
873
937
|
}
|
|
874
938
|
|
|
875
|
-
async saveBatch(
|
|
939
|
+
async saveBatch(
|
|
940
|
+
bms: Unsaved<BM>[],
|
|
941
|
+
opt: CommonDaoSaveBatchOptions<DBM> = {},
|
|
942
|
+
): Promise<Saved<BM>[]> {
|
|
876
943
|
if (!bms.length) return []
|
|
877
944
|
this.requireWriteAccess()
|
|
878
945
|
const table = opt.table || this.cfg.table
|
|
@@ -920,7 +987,7 @@ export class CommonDao<
|
|
|
920
987
|
return bms as any[]
|
|
921
988
|
}
|
|
922
989
|
|
|
923
|
-
async saveBatchAsDBM(dbms: DBM[], opt:
|
|
990
|
+
async saveBatchAsDBM(dbms: DBM[], opt: CommonDaoSaveBatchOptions<DBM> = {}): Promise<DBM[]> {
|
|
924
991
|
if (!dbms.length) return []
|
|
925
992
|
this.requireWriteAccess()
|
|
926
993
|
const table = opt.table || this.cfg.table
|
package/src/model.util.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { stringId } from '@naturalcycles/nodejs-lib'
|
|
2
1
|
import { CreatedUpdated, CreatedUpdatedId } from '@naturalcycles/js-lib'
|
|
2
|
+
import { stringId } from '@naturalcycles/nodejs-lib'
|
|
3
3
|
|
|
4
4
|
export function createdUpdatedFields(
|
|
5
5
|
existingObject?: Partial<CreatedUpdated> | null,
|
|
@@ -22,8 +22,11 @@ import {
|
|
|
22
22
|
_pathExistsSync,
|
|
23
23
|
_ensureFileSync,
|
|
24
24
|
_writeJson,
|
|
25
|
+
boldWhite,
|
|
26
|
+
dimWhite,
|
|
27
|
+
grey,
|
|
28
|
+
yellow,
|
|
25
29
|
} from '@naturalcycles/nodejs-lib'
|
|
26
|
-
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib'
|
|
27
30
|
import { CommonDB } from '../common.db'
|
|
28
31
|
import { DBQuery } from '../index'
|
|
29
32
|
|
|
@@ -9,8 +9,11 @@ import {
|
|
|
9
9
|
transformTap,
|
|
10
10
|
writableForEach,
|
|
11
11
|
_pipeline,
|
|
12
|
+
boldWhite,
|
|
13
|
+
dimWhite,
|
|
14
|
+
grey,
|
|
15
|
+
yellow,
|
|
12
16
|
} from '@naturalcycles/nodejs-lib'
|
|
13
|
-
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib'
|
|
14
17
|
import { CommonDB } from '../common.db'
|
|
15
18
|
import { CommonDBSaveOptions } from '../db.model'
|
|
16
19
|
import { DBQuery } from '../query/dbQuery'
|
|
@@ -27,8 +27,11 @@ import {
|
|
|
27
27
|
_pipeline,
|
|
28
28
|
_ensureDirSync,
|
|
29
29
|
_readJson,
|
|
30
|
+
boldWhite,
|
|
31
|
+
dimWhite,
|
|
32
|
+
grey,
|
|
33
|
+
yellow,
|
|
30
34
|
} from '@naturalcycles/nodejs-lib'
|
|
31
|
-
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib'
|
|
32
35
|
import { CommonDB } from '../common.db'
|
|
33
36
|
import { CommonDBSaveOptions } from '../index'
|
|
34
37
|
|
package/src/testing/daoTest.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Readable } from 'node:stream'
|
|
2
|
-
import { pDelay, _deepCopy, _pick, _sortBy, _omit,
|
|
2
|
+
import { pDelay, _deepCopy, _pick, _sortBy, _omit, localTimeNow } from '@naturalcycles/js-lib'
|
|
3
3
|
import { _pipeline, readableToArray, transformNoOp } from '@naturalcycles/nodejs-lib'
|
|
4
4
|
import { CommonDaoLogLevel, DBQuery } from '..'
|
|
5
5
|
import { CommonDB } from '../common.db'
|
|
@@ -289,7 +289,7 @@ export function runCommonDaoTest(
|
|
|
289
289
|
await dao.query().deleteByQuery()
|
|
290
290
|
|
|
291
291
|
// Test that id, created, updated are created
|
|
292
|
-
const now =
|
|
292
|
+
const now = localTimeNow().unix()
|
|
293
293
|
await dao.runInTransaction([dao.tx.save(_omit(item1, ['id', 'created', 'updated']))])
|
|
294
294
|
|
|
295
295
|
const loaded = await dao.query().runQuery()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { _range, _sortBy } from '@naturalcycles/js-lib'
|
|
2
2
|
import { readableToArray } from '@naturalcycles/nodejs-lib'
|
|
3
|
-
import { CommonKeyValueDao } from '../kv/commonKeyValueDao'
|
|
4
3
|
import { KeyValueDBTuple } from '../kv/commonKeyValueDB'
|
|
4
|
+
import { CommonKeyValueDao } from '../kv/commonKeyValueDao'
|
|
5
5
|
|
|
6
6
|
const testIds = _range(1, 4).map(n => `id${n}`)
|
|
7
7
|
const testEntries: KeyValueDBTuple[] = testIds.map(id => [id, Buffer.from(`${id}value`)])
|