@naturalcycles/db-lib 9.12.1 → 9.14.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/localFile.persistence.plugin.js +4 -23
- package/dist/adapter/inmemory/inMemory.db.d.ts +1 -1
- package/dist/adapter/inmemory/inMemory.db.js +3 -21
- package/dist/commondao/common.dao.d.ts +1 -1
- package/dist/commondao/common.dao.js +7 -10
- package/dist/commondao/common.dao.model.d.ts +1 -1
- package/dist/kv/commonKeyValueDao.js +2 -2
- package/dist/pipeline/dbPipelineBackup.d.ts +1 -7
- package/dist/pipeline/dbPipelineBackup.js +3 -10
- package/dist/pipeline/dbPipelineRestore.js +4 -12
- package/package.json +1 -1
- package/src/adapter/file/localFile.persistence.plugin.ts +5 -33
- package/src/adapter/inmemory/inMemory.db.ts +4 -27
- package/src/commondao/common.dao.model.ts +1 -1
- package/src/commondao/common.dao.ts +7 -14
- package/src/kv/commonKeyValueDao.ts +2 -2
- package/src/pipeline/dbPipelineBackup.ts +4 -17
- package/src/pipeline/dbPipelineRestore.ts +4 -14
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LocalFilePersistencePlugin = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
6
|
-
const promises_1 = tslib_1.__importDefault(require("node:fs/promises"));
|
|
7
4
|
const node_stream_1 = require("node:stream");
|
|
8
|
-
const node_zlib_1 = require("node:zlib");
|
|
9
5
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
10
6
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
11
7
|
/**
|
|
@@ -21,7 +17,7 @@ class LocalFilePersistencePlugin {
|
|
|
21
17
|
}
|
|
22
18
|
async ping() { }
|
|
23
19
|
async getTables() {
|
|
24
|
-
return (await
|
|
20
|
+
return (await nodejs_lib_1.fs2.readdirAsync(this.cfg.storagePath))
|
|
25
21
|
.filter(f => f.includes('.ndjson'))
|
|
26
22
|
.map(f => f.split('.ndjson')[0]);
|
|
27
23
|
}
|
|
@@ -31,31 +27,16 @@ class LocalFilePersistencePlugin {
|
|
|
31
27
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`;
|
|
32
28
|
if (!(await nodejs_lib_1.fs2.pathExistsAsync(filePath)))
|
|
33
29
|
return [];
|
|
34
|
-
|
|
35
|
-
const rows = [];
|
|
36
|
-
await (0, nodejs_lib_1._pipeline)([
|
|
37
|
-
node_fs_1.default.createReadStream(filePath),
|
|
38
|
-
...transformUnzip,
|
|
39
|
-
(0, nodejs_lib_1.transformSplit)(), // splits by \n
|
|
40
|
-
(0, nodejs_lib_1.transformJsonParse)(),
|
|
41
|
-
(0, nodejs_lib_1.writablePushToArray)(rows),
|
|
42
|
-
]);
|
|
43
|
-
return rows;
|
|
30
|
+
return await nodejs_lib_1.fs2.createReadStreamAsNDJSON(filePath).toArray();
|
|
44
31
|
}
|
|
45
32
|
async saveFiles(ops) {
|
|
46
|
-
await (0, js_lib_1.pMap)(ops, async (op) => await this.saveFile(op.table, op.rows), { concurrency:
|
|
33
|
+
await (0, js_lib_1.pMap)(ops, async (op) => await this.saveFile(op.table, op.rows), { concurrency: 32 });
|
|
47
34
|
}
|
|
48
35
|
async saveFile(table, rows) {
|
|
49
36
|
await nodejs_lib_1.fs2.ensureDirAsync(this.cfg.storagePath);
|
|
50
37
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`;
|
|
51
38
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`;
|
|
52
|
-
|
|
53
|
-
await (0, nodejs_lib_1._pipeline)([
|
|
54
|
-
node_stream_1.Readable.from(rows),
|
|
55
|
-
(0, nodejs_lib_1.transformToNDJson)(),
|
|
56
|
-
...transformZip,
|
|
57
|
-
node_fs_1.default.createWriteStream(filePath),
|
|
58
|
-
]);
|
|
39
|
+
await (0, nodejs_lib_1._pipeline)([node_stream_1.Readable.from(rows), ...nodejs_lib_1.fs2.createWriteStreamAsNDJSON(filePath)]);
|
|
59
40
|
}
|
|
60
41
|
}
|
|
61
42
|
exports.LocalFilePersistencePlugin = LocalFilePersistencePlugin;
|
|
@@ -29,7 +29,7 @@ export interface InMemoryDBCfg {
|
|
|
29
29
|
*/
|
|
30
30
|
persistenceEnabled: boolean;
|
|
31
31
|
/**
|
|
32
|
-
* @default ./tmp/inmemorydb
|
|
32
|
+
* @default ./tmp/inmemorydb.ndjson.gz
|
|
33
33
|
*
|
|
34
34
|
* Will store one ndjson file per table.
|
|
35
35
|
* Will only flush on demand (see .flushToDisk() and .restoreFromDisk() methods).
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.InMemoryDBTransaction = exports.InMemoryDB = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
6
|
-
const promises_1 = tslib_1.__importDefault(require("node:fs/promises"));
|
|
7
4
|
const node_stream_1 = require("node:stream");
|
|
8
|
-
const node_zlib_1 = require("node:zlib");
|
|
9
5
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
10
6
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
11
7
|
const __1 = require("../..");
|
|
@@ -169,7 +165,6 @@ class InMemoryDB {
|
|
|
169
165
|
const { persistentStoragePath, persistZip } = this.cfg;
|
|
170
166
|
const started = Date.now();
|
|
171
167
|
await nodejs_lib_1.fs2.emptyDirAsync(persistentStoragePath);
|
|
172
|
-
const transformZip = persistZip ? [(0, node_zlib_1.createGzip)()] : [];
|
|
173
168
|
let tables = 0;
|
|
174
169
|
// infinite concurrency for now
|
|
175
170
|
await (0, js_lib_1.pMap)(Object.keys(this.data), async (table) => {
|
|
@@ -178,12 +173,7 @@ class InMemoryDB {
|
|
|
178
173
|
return; // 0 rows
|
|
179
174
|
tables++;
|
|
180
175
|
const fname = `${persistentStoragePath}/${table}.ndjson${persistZip ? '.gz' : ''}`;
|
|
181
|
-
await (0, nodejs_lib_1._pipeline)([
|
|
182
|
-
node_stream_1.Readable.from(rows),
|
|
183
|
-
(0, nodejs_lib_1.transformToNDJson)(),
|
|
184
|
-
...transformZip,
|
|
185
|
-
node_fs_1.default.createWriteStream(fname),
|
|
186
|
-
]);
|
|
176
|
+
await (0, nodejs_lib_1._pipeline)([node_stream_1.Readable.from(rows), ...nodejs_lib_1.fs2.createWriteStreamAsNDJSON(fname)]);
|
|
187
177
|
});
|
|
188
178
|
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
179
|
}
|
|
@@ -196,20 +186,12 @@ class InMemoryDB {
|
|
|
196
186
|
const started = Date.now();
|
|
197
187
|
await nodejs_lib_1.fs2.ensureDirAsync(persistentStoragePath);
|
|
198
188
|
this.data = {}; // empty it in the beginning!
|
|
199
|
-
const files = (await
|
|
189
|
+
const files = (await nodejs_lib_1.fs2.readdirAsync(persistentStoragePath)).filter(f => f.includes('.ndjson'));
|
|
200
190
|
// infinite concurrency for now
|
|
201
191
|
await (0, js_lib_1.pMap)(files, async (file) => {
|
|
202
192
|
const fname = `${persistentStoragePath}/${file}`;
|
|
203
193
|
const table = file.split('.ndjson')[0];
|
|
204
|
-
const
|
|
205
|
-
const rows = [];
|
|
206
|
-
await (0, nodejs_lib_1._pipeline)([
|
|
207
|
-
node_fs_1.default.createReadStream(fname),
|
|
208
|
-
...transformUnzip,
|
|
209
|
-
(0, nodejs_lib_1.transformSplit)(), // splits by \n
|
|
210
|
-
(0, nodejs_lib_1.transformJsonParse)(),
|
|
211
|
-
(0, nodejs_lib_1.writablePushToArray)(rows),
|
|
212
|
-
]);
|
|
194
|
+
const rows = await nodejs_lib_1.fs2.createReadStreamAsNDJSON(fname).toArray();
|
|
213
195
|
this.data[table] = (0, js_lib_1._by)(rows, r => r.id);
|
|
214
196
|
});
|
|
215
197
|
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`);
|
|
@@ -114,7 +114,7 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
|
|
|
114
114
|
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
115
115
|
* (of size opt.chunkSize, which defaults to 500),
|
|
116
116
|
* and then executing db.saveBatch(chunk) with the concurrency
|
|
117
|
-
* of opt.chunkConcurrency (which defaults to
|
|
117
|
+
* of opt.chunkConcurrency (which defaults to 32).
|
|
118
118
|
*/
|
|
119
119
|
streamSaveTransform(opt?: CommonDaoStreamSaveOptions<DBM>): Transform[];
|
|
120
120
|
/**
|
|
@@ -423,8 +423,7 @@ class CommonDao {
|
|
|
423
423
|
const started = this.logStarted(op, q.table, true);
|
|
424
424
|
let count = 0;
|
|
425
425
|
await (0, nodejs_lib_1._pipeline)([
|
|
426
|
-
this.cfg.db.streamQuery(q.select(['id']), opt)
|
|
427
|
-
(0, nodejs_lib_1.transformMapSimple)(r => {
|
|
426
|
+
this.cfg.db.streamQuery(q.select(['id']), opt).map(r => {
|
|
428
427
|
count++;
|
|
429
428
|
return r.id;
|
|
430
429
|
}),
|
|
@@ -699,7 +698,7 @@ class CommonDao {
|
|
|
699
698
|
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
700
699
|
* (of size opt.chunkSize, which defaults to 500),
|
|
701
700
|
* and then executing db.saveBatch(chunk) with the concurrency
|
|
702
|
-
* of opt.chunkConcurrency (which defaults to
|
|
701
|
+
* of opt.chunkConcurrency (which defaults to 32).
|
|
703
702
|
*/
|
|
704
703
|
streamSaveTransform(opt = {}) {
|
|
705
704
|
this.requireWriteAccess();
|
|
@@ -711,7 +710,7 @@ class CommonDao {
|
|
|
711
710
|
}
|
|
712
711
|
const excludeFromIndexes = opt.excludeFromIndexes || this.cfg.excludeFromIndexes;
|
|
713
712
|
const { beforeSave } = this.cfg.hooks;
|
|
714
|
-
const { chunkSize = 500, chunkConcurrency =
|
|
713
|
+
const { chunkSize = 500, chunkConcurrency = 32, errorMode } = opt;
|
|
715
714
|
return [
|
|
716
715
|
(0, nodejs_lib_1.transformMap)(async (bm) => {
|
|
717
716
|
this.assignIdCreatedUpdated(bm, opt); // mutates
|
|
@@ -780,15 +779,13 @@ class CommonDao {
|
|
|
780
779
|
const started = this.logStarted(op, q.table);
|
|
781
780
|
let deleted = 0;
|
|
782
781
|
if (opt.chunkSize) {
|
|
783
|
-
const { chunkSize, chunkConcurrency =
|
|
782
|
+
const { chunkSize, chunkConcurrency = 32 } = opt;
|
|
784
783
|
await (0, nodejs_lib_1._pipeline)([
|
|
785
|
-
this.cfg.db.streamQuery(q.select(['id']), opt),
|
|
786
|
-
(0, nodejs_lib_1.transformMapSimple)(r => r.id, {
|
|
787
|
-
errorMode: js_lib_1.ErrorMode.SUPPRESS,
|
|
788
|
-
}),
|
|
784
|
+
this.cfg.db.streamQuery(q.select(['id']), opt).map(r => r.id),
|
|
789
785
|
(0, nodejs_lib_1.transformChunk)({ chunkSize }),
|
|
790
786
|
(0, nodejs_lib_1.transformMap)(async (ids) => {
|
|
791
|
-
|
|
787
|
+
await this.cfg.db.deleteByIds(q.table, ids, opt);
|
|
788
|
+
deleted += ids.length;
|
|
792
789
|
}, {
|
|
793
790
|
predicate: js_lib_1._passthroughPredicate,
|
|
794
791
|
concurrency: chunkConcurrency,
|
|
@@ -257,7 +257,7 @@ export interface CommonDaoStreamOptions<IN> extends CommonDaoOptions, TransformL
|
|
|
257
257
|
chunkSize?: number;
|
|
258
258
|
/**
|
|
259
259
|
* When chunkSize is set - this option controls how many chunks to run concurrently.
|
|
260
|
-
* Defaults to
|
|
260
|
+
* Defaults to 32.
|
|
261
261
|
*/
|
|
262
262
|
chunkConcurrency?: number;
|
|
263
263
|
}
|
|
@@ -137,7 +137,7 @@ class CommonKeyValueDao {
|
|
|
137
137
|
return []; // SKIP
|
|
138
138
|
}
|
|
139
139
|
}, {
|
|
140
|
-
concurrency:
|
|
140
|
+
concurrency: 32,
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
143
|
streamEntries(limit) {
|
|
@@ -154,7 +154,7 @@ class CommonKeyValueDao {
|
|
|
154
154
|
return []; // SKIP
|
|
155
155
|
}
|
|
156
156
|
}, {
|
|
157
|
-
concurrency:
|
|
157
|
+
concurrency: 32,
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { ZlibOptions } from 'node:zlib';
|
|
3
1
|
import { AsyncMapper, ErrorMode, UnixTimestampNumber, StringMap } from '@naturalcycles/js-lib';
|
|
4
2
|
import { NDJsonStats, TransformLogProgressOptions, TransformMapOptions } from '@naturalcycles/nodejs-lib';
|
|
5
3
|
import { CommonDB } from '../common.db';
|
|
@@ -65,8 +63,8 @@ export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
|
65
63
|
gzip?: boolean;
|
|
66
64
|
/**
|
|
67
65
|
* Only applicable if `gzip` is enabled
|
|
66
|
+
* Currently not available.
|
|
68
67
|
*/
|
|
69
|
-
zlibOptions?: ZlibOptions;
|
|
70
68
|
/**
|
|
71
69
|
* Optionally you can provide mapper that is going to run for each table.
|
|
72
70
|
*
|
|
@@ -96,10 +94,6 @@ export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
|
96
94
|
* If true - will use CommonDB.getTableSchema() and emit schema.
|
|
97
95
|
*/
|
|
98
96
|
emitSchemaFromDB?: boolean;
|
|
99
|
-
/**
|
|
100
|
-
* @default false
|
|
101
|
-
*/
|
|
102
|
-
sortObjects?: boolean;
|
|
103
97
|
}
|
|
104
98
|
/**
|
|
105
99
|
* Pipeline from input stream(s) to a NDJSON file (optionally gzipped).
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dbPipelineBackup = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
6
|
-
const promises_1 = tslib_1.__importDefault(require("node:fs/promises"));
|
|
7
|
-
const node_zlib_1 = require("node:zlib");
|
|
8
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
9
5
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
10
6
|
const index_1 = require("../index");
|
|
@@ -18,8 +14,7 @@ const index_1 = require("../index");
|
|
|
18
14
|
* Optionally you can provide mapperPerTable and @param transformMapOptions (one for all mappers) - it will run for each table.
|
|
19
15
|
*/
|
|
20
16
|
async function dbPipelineBackup(opt) {
|
|
21
|
-
const { db, concurrency = 16, limit = 0, outputDirPath, protectFromOverwrite = false,
|
|
22
|
-
const strict = errorMode !== js_lib_1.ErrorMode.SUPPRESS;
|
|
17
|
+
const { db, concurrency = 16, limit = 0, outputDirPath, protectFromOverwrite = false, mapperPerTable = {}, queryPerTable = {}, logEveryPerTable = {}, transformMapOptions, errorMode = js_lib_1.ErrorMode.SUPPRESS, emitSchemaFromDB = false, } = opt;
|
|
23
18
|
const gzip = opt.gzip !== false; // default to true
|
|
24
19
|
let { tables } = opt;
|
|
25
20
|
console.log(`>> ${(0, nodejs_lib_1.dimWhite)('dbPipelineBackup')} started in ${(0, nodejs_lib_1.grey)(outputDirPath)}...`);
|
|
@@ -74,11 +69,9 @@ async function dbPipelineBackup(opt) {
|
|
|
74
69
|
(0, nodejs_lib_1.transformTap)(() => {
|
|
75
70
|
rows++;
|
|
76
71
|
}),
|
|
77
|
-
|
|
78
|
-
...(gzip ? [(0, node_zlib_1.createGzip)(zlibOptions)] : []), // optional gzip
|
|
79
|
-
node_fs_1.default.createWriteStream(filePath),
|
|
72
|
+
...nodejs_lib_1.fs2.createWriteStreamAsNDJSON(filePath),
|
|
80
73
|
]);
|
|
81
|
-
const { size: sizeBytes } = await
|
|
74
|
+
const { size: sizeBytes } = await nodejs_lib_1.fs2.statAsync(filePath);
|
|
82
75
|
const stats = nodejs_lib_1.NDJsonStats.create({
|
|
83
76
|
tookMillis: Date.now() - started,
|
|
84
77
|
rows,
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dbPipelineRestore = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
6
|
-
const node_zlib_1 = require("node:zlib");
|
|
7
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
8
5
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
9
6
|
/**
|
|
@@ -15,7 +12,6 @@ const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
|
15
12
|
*/
|
|
16
13
|
async function dbPipelineRestore(opt) {
|
|
17
14
|
const { db, concurrency = 16, chunkSize = 100, limit, sinceUpdated, inputDirPath, mapperPerTable = {}, saveOptionsPerTable = {}, transformMapOptions, errorMode = js_lib_1.ErrorMode.SUPPRESS, recreateTables = false, } = opt;
|
|
18
|
-
const strict = errorMode !== js_lib_1.ErrorMode.SUPPRESS;
|
|
19
15
|
const onlyTables = opt.tables && new Set(opt.tables);
|
|
20
16
|
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0, nodejs_lib_1.grey)((0, js_lib_1.localTime)(sinceUpdated).toPretty()) : '';
|
|
21
17
|
console.log(`>> ${(0, nodejs_lib_1.dimWhite)('dbPipelineRestore')} started in ${(0, nodejs_lib_1.grey)(inputDirPath)}...${sinceUpdatedStr}`);
|
|
@@ -24,7 +20,7 @@ async function dbPipelineRestore(opt) {
|
|
|
24
20
|
const sizeByTable = {};
|
|
25
21
|
const statsPerTable = {};
|
|
26
22
|
const tables = [];
|
|
27
|
-
|
|
23
|
+
nodejs_lib_1.fs2.readdir(inputDirPath).forEach(f => {
|
|
28
24
|
let table;
|
|
29
25
|
let gzip = false;
|
|
30
26
|
if (f.endsWith('.ndjson')) {
|
|
@@ -42,7 +38,7 @@ async function dbPipelineRestore(opt) {
|
|
|
42
38
|
tables.push(table);
|
|
43
39
|
if (gzip)
|
|
44
40
|
tablesToGzip.add(table);
|
|
45
|
-
sizeByTable[table] =
|
|
41
|
+
sizeByTable[table] = nodejs_lib_1.fs2.stat(`${inputDirPath}/${f}`).size;
|
|
46
42
|
});
|
|
47
43
|
const sizeStrByTable = (0, js_lib_1._mapValues)(sizeByTable, (_k, b) => (0, js_lib_1._hb)(b));
|
|
48
44
|
console.log(`${(0, nodejs_lib_1.yellow)(tables.length)} ${(0, nodejs_lib_1.boldWhite)('table(s)')}:\n`, sizeStrByTable);
|
|
@@ -50,7 +46,7 @@ async function dbPipelineRestore(opt) {
|
|
|
50
46
|
if (recreateTables) {
|
|
51
47
|
await (0, js_lib_1.pMap)(tables, async (table) => {
|
|
52
48
|
const schemaFilePath = `${inputDirPath}/${table}.schema.json`;
|
|
53
|
-
if (!
|
|
49
|
+
if (!nodejs_lib_1.fs2.pathExists(schemaFilePath)) {
|
|
54
50
|
console.warn(`${schemaFilePath} does not exist!`);
|
|
55
51
|
return;
|
|
56
52
|
}
|
|
@@ -67,17 +63,13 @@ async function dbPipelineRestore(opt) {
|
|
|
67
63
|
const sizeBytes = sizeByTable[table];
|
|
68
64
|
console.log(`<< ${(0, nodejs_lib_1.grey)(filePath)} ${(0, nodejs_lib_1.dimWhite)((0, js_lib_1._hb)(sizeBytes))} started...`);
|
|
69
65
|
await (0, nodejs_lib_1._pipeline)([
|
|
70
|
-
|
|
71
|
-
...(gzip ? [(0, node_zlib_1.createUnzip)()] : []),
|
|
72
|
-
(0, nodejs_lib_1.transformSplit)(), // splits by \n
|
|
73
|
-
(0, nodejs_lib_1.transformJsonParse)({ strict }),
|
|
66
|
+
nodejs_lib_1.fs2.createReadStreamAsNDJSON(filePath).take(limit || Number.POSITIVE_INFINITY),
|
|
74
67
|
(0, nodejs_lib_1.transformTap)(() => rows++),
|
|
75
68
|
(0, nodejs_lib_1.transformLogProgress)({
|
|
76
69
|
logEvery: 1000,
|
|
77
70
|
...opt,
|
|
78
71
|
metric: table,
|
|
79
72
|
}),
|
|
80
|
-
(0, nodejs_lib_1.transformLimit)({ limit }),
|
|
81
73
|
...(sinceUpdated
|
|
82
74
|
? [(0, nodejs_lib_1.transformFilterSync)(r => r.updated >= sinceUpdated)]
|
|
83
75
|
: []),
|
package/package.json
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import fsp from 'node:fs/promises'
|
|
3
1
|
import { Readable } from 'node:stream'
|
|
4
|
-
import { createGzip, createUnzip } from 'node:zlib'
|
|
5
2
|
import { ObjectWithId, pMap } from '@naturalcycles/js-lib'
|
|
6
|
-
import {
|
|
7
|
-
transformJsonParse,
|
|
8
|
-
transformSplit,
|
|
9
|
-
transformToNDJson,
|
|
10
|
-
writablePushToArray,
|
|
11
|
-
_pipeline,
|
|
12
|
-
fs2,
|
|
13
|
-
} from '@naturalcycles/nodejs-lib'
|
|
3
|
+
import { _pipeline, fs2 } from '@naturalcycles/nodejs-lib'
|
|
14
4
|
import { DBSaveBatchOperation } from '../../db.model'
|
|
15
5
|
import { FileDBPersistencePlugin } from './file.db.model'
|
|
16
6
|
|
|
@@ -43,7 +33,7 @@ export class LocalFilePersistencePlugin implements FileDBPersistencePlugin {
|
|
|
43
33
|
async ping(): Promise<void> {}
|
|
44
34
|
|
|
45
35
|
async getTables(): Promise<string[]> {
|
|
46
|
-
return (await
|
|
36
|
+
return (await fs2.readdirAsync(this.cfg.storagePath))
|
|
47
37
|
.filter(f => f.includes('.ndjson'))
|
|
48
38
|
.map(f => f.split('.ndjson')[0]!)
|
|
49
39
|
}
|
|
@@ -55,36 +45,18 @@ export class LocalFilePersistencePlugin implements FileDBPersistencePlugin {
|
|
|
55
45
|
|
|
56
46
|
if (!(await fs2.pathExistsAsync(filePath))) return []
|
|
57
47
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const rows: ROW[] = []
|
|
61
|
-
|
|
62
|
-
await _pipeline([
|
|
63
|
-
fs.createReadStream(filePath),
|
|
64
|
-
...transformUnzip,
|
|
65
|
-
transformSplit(), // splits by \n
|
|
66
|
-
transformJsonParse(),
|
|
67
|
-
writablePushToArray(rows),
|
|
68
|
-
])
|
|
69
|
-
|
|
70
|
-
return rows
|
|
48
|
+
return await fs2.createReadStreamAsNDJSON(filePath).toArray()
|
|
71
49
|
}
|
|
72
50
|
|
|
73
51
|
async saveFiles(ops: DBSaveBatchOperation<any>[]): Promise<void> {
|
|
74
|
-
await pMap(ops, async op => await this.saveFile(op.table, op.rows), { concurrency:
|
|
52
|
+
await pMap(ops, async op => await this.saveFile(op.table, op.rows), { concurrency: 32 })
|
|
75
53
|
}
|
|
76
54
|
|
|
77
55
|
async saveFile<ROW extends ObjectWithId>(table: string, rows: ROW[]): Promise<void> {
|
|
78
56
|
await fs2.ensureDirAsync(this.cfg.storagePath)
|
|
79
57
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`
|
|
80
58
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`
|
|
81
|
-
const transformZip = this.cfg.gzip ? [createGzip()] : []
|
|
82
59
|
|
|
83
|
-
await _pipeline([
|
|
84
|
-
Readable.from(rows),
|
|
85
|
-
transformToNDJson(),
|
|
86
|
-
...transformZip,
|
|
87
|
-
fs.createWriteStream(filePath),
|
|
88
|
-
])
|
|
60
|
+
await _pipeline([Readable.from(rows), ...fs2.createWriteStreamAsNDJSON(filePath)])
|
|
89
61
|
}
|
|
90
62
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import fsp from 'node:fs/promises'
|
|
3
1
|
import { Readable } from 'node:stream'
|
|
4
|
-
import { createGzip, createUnzip } from 'node:zlib'
|
|
5
2
|
import {
|
|
6
3
|
generateJsonSchemaFromData,
|
|
7
4
|
JsonSchemaObject,
|
|
@@ -20,10 +17,6 @@ import {
|
|
|
20
17
|
import {
|
|
21
18
|
bufferReviver,
|
|
22
19
|
ReadableTyped,
|
|
23
|
-
transformJsonParse,
|
|
24
|
-
transformSplit,
|
|
25
|
-
transformToNDJson,
|
|
26
|
-
writablePushToArray,
|
|
27
20
|
_pipeline,
|
|
28
21
|
dimGrey,
|
|
29
22
|
yellow,
|
|
@@ -78,7 +71,7 @@ export interface InMemoryDBCfg {
|
|
|
78
71
|
persistenceEnabled: boolean
|
|
79
72
|
|
|
80
73
|
/**
|
|
81
|
-
* @default ./tmp/inmemorydb
|
|
74
|
+
* @default ./tmp/inmemorydb.ndjson.gz
|
|
82
75
|
*
|
|
83
76
|
* Will store one ndjson file per table.
|
|
84
77
|
* Will only flush on demand (see .flushToDisk() and .restoreFromDisk() methods).
|
|
@@ -312,7 +305,6 @@ export class InMemoryDB implements CommonDB {
|
|
|
312
305
|
|
|
313
306
|
await fs2.emptyDirAsync(persistentStoragePath)
|
|
314
307
|
|
|
315
|
-
const transformZip = persistZip ? [createGzip()] : []
|
|
316
308
|
let tables = 0
|
|
317
309
|
|
|
318
310
|
// infinite concurrency for now
|
|
@@ -323,12 +315,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
323
315
|
tables++
|
|
324
316
|
const fname = `${persistentStoragePath}/${table}.ndjson${persistZip ? '.gz' : ''}`
|
|
325
317
|
|
|
326
|
-
await _pipeline([
|
|
327
|
-
Readable.from(rows),
|
|
328
|
-
transformToNDJson(),
|
|
329
|
-
...transformZip,
|
|
330
|
-
fs.createWriteStream(fname),
|
|
331
|
-
])
|
|
318
|
+
await _pipeline([Readable.from(rows), ...fs2.createWriteStreamAsNDJSON(fname)])
|
|
332
319
|
})
|
|
333
320
|
|
|
334
321
|
this.cfg.logger!.log(
|
|
@@ -349,24 +336,14 @@ export class InMemoryDB implements CommonDB {
|
|
|
349
336
|
|
|
350
337
|
this.data = {} // empty it in the beginning!
|
|
351
338
|
|
|
352
|
-
const files = (await
|
|
339
|
+
const files = (await fs2.readdirAsync(persistentStoragePath)).filter(f => f.includes('.ndjson'))
|
|
353
340
|
|
|
354
341
|
// infinite concurrency for now
|
|
355
342
|
await pMap(files, async file => {
|
|
356
343
|
const fname = `${persistentStoragePath}/${file}`
|
|
357
344
|
const table = file.split('.ndjson')[0]!
|
|
358
345
|
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
const rows: any[] = []
|
|
362
|
-
|
|
363
|
-
await _pipeline([
|
|
364
|
-
fs.createReadStream(fname),
|
|
365
|
-
...transformUnzip,
|
|
366
|
-
transformSplit(), // splits by \n
|
|
367
|
-
transformJsonParse(),
|
|
368
|
-
writablePushToArray(rows),
|
|
369
|
-
])
|
|
346
|
+
const rows = await fs2.createReadStreamAsNDJSON(fname).toArray()
|
|
370
347
|
|
|
371
348
|
this.data[table] = _by(rows, r => r.id)
|
|
372
349
|
})
|
|
@@ -321,7 +321,7 @@ export interface CommonDaoStreamOptions<IN>
|
|
|
321
321
|
|
|
322
322
|
/**
|
|
323
323
|
* When chunkSize is set - this option controls how many chunks to run concurrently.
|
|
324
|
-
* Defaults to
|
|
324
|
+
* Defaults to 32.
|
|
325
325
|
*/
|
|
326
326
|
chunkConcurrency?: number
|
|
327
327
|
}
|
|
@@ -40,7 +40,6 @@ import {
|
|
|
40
40
|
transformChunk,
|
|
41
41
|
transformLogProgress,
|
|
42
42
|
transformMap,
|
|
43
|
-
transformMapSimple,
|
|
44
43
|
transformNoOp,
|
|
45
44
|
writableVoid,
|
|
46
45
|
} from '@naturalcycles/nodejs-lib'
|
|
@@ -580,8 +579,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
|
|
|
580
579
|
let count = 0
|
|
581
580
|
|
|
582
581
|
await _pipeline([
|
|
583
|
-
this.cfg.db.streamQuery<DBM>(q.select(['id']), opt)
|
|
584
|
-
transformMapSimple<DBM, string>(r => {
|
|
582
|
+
this.cfg.db.streamQuery<DBM>(q.select(['id']), opt).map(r => {
|
|
585
583
|
count++
|
|
586
584
|
return r.id
|
|
587
585
|
}),
|
|
@@ -920,7 +918,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
|
|
|
920
918
|
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
921
919
|
* (of size opt.chunkSize, which defaults to 500),
|
|
922
920
|
* and then executing db.saveBatch(chunk) with the concurrency
|
|
923
|
-
* of opt.chunkConcurrency (which defaults to
|
|
921
|
+
* of opt.chunkConcurrency (which defaults to 32).
|
|
924
922
|
*/
|
|
925
923
|
streamSaveTransform(opt: CommonDaoStreamSaveOptions<DBM> = {}): Transform[] {
|
|
926
924
|
this.requireWriteAccess()
|
|
@@ -936,7 +934,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
|
|
|
936
934
|
const excludeFromIndexes = opt.excludeFromIndexes || this.cfg.excludeFromIndexes
|
|
937
935
|
const { beforeSave } = this.cfg.hooks!
|
|
938
936
|
|
|
939
|
-
const { chunkSize = 500, chunkConcurrency =
|
|
937
|
+
const { chunkSize = 500, chunkConcurrency = 32, errorMode } = opt
|
|
940
938
|
|
|
941
939
|
return [
|
|
942
940
|
transformMap<BM, DBM>(
|
|
@@ -1019,20 +1017,15 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
|
|
|
1019
1017
|
let deleted = 0
|
|
1020
1018
|
|
|
1021
1019
|
if (opt.chunkSize) {
|
|
1022
|
-
const { chunkSize, chunkConcurrency =
|
|
1020
|
+
const { chunkSize, chunkConcurrency = 32 } = opt
|
|
1023
1021
|
|
|
1024
1022
|
await _pipeline([
|
|
1025
|
-
this.cfg.db.streamQuery<DBM>(q.select(['id']), opt),
|
|
1026
|
-
transformMapSimple<ObjectWithId, string>(r => r.id, {
|
|
1027
|
-
errorMode: ErrorMode.SUPPRESS,
|
|
1028
|
-
}),
|
|
1023
|
+
this.cfg.db.streamQuery<DBM>(q.select(['id']), opt).map(r => r.id),
|
|
1029
1024
|
transformChunk<string>({ chunkSize }),
|
|
1030
1025
|
transformMap<string[], void>(
|
|
1031
1026
|
async ids => {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
opt,
|
|
1035
|
-
)
|
|
1027
|
+
await this.cfg.db.deleteByIds(q.table, ids, opt)
|
|
1028
|
+
deleted += ids.length
|
|
1036
1029
|
},
|
|
1037
1030
|
{
|
|
1038
1031
|
predicate: _passthroughPredicate,
|
|
@@ -224,7 +224,7 @@ export class CommonKeyValueDao<T> {
|
|
|
224
224
|
}
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
|
-
concurrency:
|
|
227
|
+
concurrency: 32,
|
|
228
228
|
},
|
|
229
229
|
)
|
|
230
230
|
}
|
|
@@ -248,7 +248,7 @@ export class CommonKeyValueDao<T> {
|
|
|
248
248
|
}
|
|
249
249
|
},
|
|
250
250
|
{
|
|
251
|
-
concurrency:
|
|
251
|
+
concurrency: 32,
|
|
252
252
|
},
|
|
253
253
|
)
|
|
254
254
|
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import fsp from 'node:fs/promises'
|
|
3
|
-
import { createGzip, ZlibOptions } from 'node:zlib'
|
|
4
1
|
import {
|
|
5
2
|
AppError,
|
|
6
3
|
AsyncMapper,
|
|
@@ -18,7 +15,6 @@ import {
|
|
|
18
15
|
transformMap,
|
|
19
16
|
TransformMapOptions,
|
|
20
17
|
transformTap,
|
|
21
|
-
transformToNDJson,
|
|
22
18
|
_pipeline,
|
|
23
19
|
boldWhite,
|
|
24
20
|
dimWhite,
|
|
@@ -101,8 +97,9 @@ export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
|
101
97
|
|
|
102
98
|
/**
|
|
103
99
|
* Only applicable if `gzip` is enabled
|
|
100
|
+
* Currently not available.
|
|
104
101
|
*/
|
|
105
|
-
zlibOptions?: ZlibOptions
|
|
102
|
+
// zlibOptions?: ZlibOptions
|
|
106
103
|
|
|
107
104
|
/**
|
|
108
105
|
* Optionally you can provide mapper that is going to run for each table.
|
|
@@ -138,11 +135,6 @@ export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
|
138
135
|
* If true - will use CommonDB.getTableSchema() and emit schema.
|
|
139
136
|
*/
|
|
140
137
|
emitSchemaFromDB?: boolean
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* @default false
|
|
144
|
-
*/
|
|
145
|
-
sortObjects?: boolean
|
|
146
138
|
}
|
|
147
139
|
|
|
148
140
|
/**
|
|
@@ -161,16 +153,13 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
161
153
|
limit = 0,
|
|
162
154
|
outputDirPath,
|
|
163
155
|
protectFromOverwrite = false,
|
|
164
|
-
zlibOptions,
|
|
165
156
|
mapperPerTable = {},
|
|
166
157
|
queryPerTable = {},
|
|
167
158
|
logEveryPerTable = {},
|
|
168
159
|
transformMapOptions,
|
|
169
160
|
errorMode = ErrorMode.SUPPRESS,
|
|
170
161
|
emitSchemaFromDB = false,
|
|
171
|
-
sortObjects = false,
|
|
172
162
|
} = opt
|
|
173
|
-
const strict = errorMode !== ErrorMode.SUPPRESS
|
|
174
163
|
const gzip = opt.gzip !== false // default to true
|
|
175
164
|
|
|
176
165
|
let { tables } = opt
|
|
@@ -243,12 +232,10 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
243
232
|
transformTap(() => {
|
|
244
233
|
rows++
|
|
245
234
|
}),
|
|
246
|
-
|
|
247
|
-
...(gzip ? [createGzip(zlibOptions)] : []), // optional gzip
|
|
248
|
-
fs.createWriteStream(filePath),
|
|
235
|
+
...fs2.createWriteStreamAsNDJSON(filePath),
|
|
249
236
|
])
|
|
250
237
|
|
|
251
|
-
const { size: sizeBytes } = await
|
|
238
|
+
const { size: sizeBytes } = await fs2.statAsync(filePath)
|
|
252
239
|
|
|
253
240
|
const stats = NDJsonStats.create({
|
|
254
241
|
tookMillis: Date.now() - started,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import { createUnzip } from 'node:zlib'
|
|
3
1
|
import {
|
|
4
2
|
AsyncMapper,
|
|
5
3
|
ErrorMode,
|
|
@@ -15,13 +13,10 @@ import {
|
|
|
15
13
|
NDJsonStats,
|
|
16
14
|
transformChunk,
|
|
17
15
|
transformFilterSync,
|
|
18
|
-
transformJsonParse,
|
|
19
|
-
transformLimit,
|
|
20
16
|
transformLogProgress,
|
|
21
17
|
TransformLogProgressOptions,
|
|
22
18
|
transformMap,
|
|
23
19
|
TransformMapOptions,
|
|
24
|
-
transformSplit,
|
|
25
20
|
transformTap,
|
|
26
21
|
writableForEach,
|
|
27
22
|
_pipeline,
|
|
@@ -135,7 +130,6 @@ export async function dbPipelineRestore(opt: DBPipelineRestoreOptions): Promise<
|
|
|
135
130
|
errorMode = ErrorMode.SUPPRESS,
|
|
136
131
|
recreateTables = false,
|
|
137
132
|
} = opt
|
|
138
|
-
const strict = errorMode !== ErrorMode.SUPPRESS
|
|
139
133
|
const onlyTables = opt.tables && new Set(opt.tables)
|
|
140
134
|
|
|
141
135
|
const sinceUpdatedStr = sinceUpdated ? ' since ' + grey(localTime(sinceUpdated).toPretty()) : ''
|
|
@@ -150,7 +144,7 @@ export async function dbPipelineRestore(opt: DBPipelineRestoreOptions): Promise<
|
|
|
150
144
|
const sizeByTable: Record<string, number> = {}
|
|
151
145
|
const statsPerTable: Record<string, NDJsonStats> = {}
|
|
152
146
|
const tables: string[] = []
|
|
153
|
-
|
|
147
|
+
fs2.readdir(inputDirPath).forEach(f => {
|
|
154
148
|
let table: string
|
|
155
149
|
let gzip = false
|
|
156
150
|
|
|
@@ -167,7 +161,7 @@ export async function dbPipelineRestore(opt: DBPipelineRestoreOptions): Promise<
|
|
|
167
161
|
|
|
168
162
|
tables.push(table)
|
|
169
163
|
if (gzip) tablesToGzip.add(table)
|
|
170
|
-
sizeByTable[table] =
|
|
164
|
+
sizeByTable[table] = fs2.stat(`${inputDirPath}/${f}`).size
|
|
171
165
|
})
|
|
172
166
|
|
|
173
167
|
const sizeStrByTable = _mapValues(sizeByTable, (_k, b) => _hb(b))
|
|
@@ -179,7 +173,7 @@ export async function dbPipelineRestore(opt: DBPipelineRestoreOptions): Promise<
|
|
|
179
173
|
if (recreateTables) {
|
|
180
174
|
await pMap(tables, async table => {
|
|
181
175
|
const schemaFilePath = `${inputDirPath}/${table}.schema.json`
|
|
182
|
-
if (!
|
|
176
|
+
if (!fs2.pathExists(schemaFilePath)) {
|
|
183
177
|
console.warn(`${schemaFilePath} does not exist!`)
|
|
184
178
|
return
|
|
185
179
|
}
|
|
@@ -204,17 +198,13 @@ export async function dbPipelineRestore(opt: DBPipelineRestoreOptions): Promise<
|
|
|
204
198
|
console.log(`<< ${grey(filePath)} ${dimWhite(_hb(sizeBytes))} started...`)
|
|
205
199
|
|
|
206
200
|
await _pipeline([
|
|
207
|
-
|
|
208
|
-
...(gzip ? [createUnzip()] : []),
|
|
209
|
-
transformSplit(), // splits by \n
|
|
210
|
-
transformJsonParse({ strict }),
|
|
201
|
+
fs2.createReadStreamAsNDJSON(filePath).take(limit || Number.POSITIVE_INFINITY),
|
|
211
202
|
transformTap(() => rows++),
|
|
212
203
|
transformLogProgress({
|
|
213
204
|
logEvery: 1000,
|
|
214
205
|
...opt,
|
|
215
206
|
metric: table,
|
|
216
207
|
}),
|
|
217
|
-
transformLimit({ limit }),
|
|
218
208
|
...(sinceUpdated
|
|
219
209
|
? [transformFilterSync<BaseDBEntity>(r => r.updated >= sinceUpdated)]
|
|
220
210
|
: []),
|