@naturalcycles/db-lib 8.54.5 → 8.55.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.
- package/dist/adapter/file/file.db.js +1 -2
- package/dist/adapter/file/localFile.persistence.plugin.js +6 -5
- package/dist/adapter/inmemory/inMemory.db.js +8 -8
- package/dist/commondao/common.dao.d.ts +14 -7
- package/dist/commondao/common.dao.js +55 -2
- package/dist/commondao/common.dao.model.d.ts +18 -2
- package/dist/pipeline/dbPipelineBackup.js +11 -11
- package/dist/pipeline/dbPipelineCopy.js +4 -5
- package/dist/pipeline/dbPipelineRestore.js +11 -11
- package/dist/query/dbQuery.d.ts +5 -7
- package/dist/testing/daoTest.js +11 -1
- package/package.json +2 -2
- package/src/adapter/file/file.db.ts +1 -2
- package/src/adapter/file/localFile.persistence.plugin.ts +2 -2
- package/src/adapter/inmemory/inMemory.db.ts +4 -3
- package/src/commondao/common.dao.model.ts +26 -4
- package/src/commondao/common.dao.ts +79 -6
- package/src/model.util.ts +1 -1
- package/src/pipeline/dbPipelineBackup.ts +6 -3
- package/src/pipeline/dbPipelineCopy.ts +4 -1
- package/src/pipeline/dbPipelineRestore.ts +5 -2
- package/src/query/dbQuery.ts +5 -6
- package/src/testing/daoTest.ts +17 -3
- 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;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LocalFilePersistencePlugin = void 0;
|
|
4
|
-
const
|
|
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"));
|
|
5
7
|
const node_stream_1 = require("node:stream");
|
|
6
|
-
const fsp = require("node:fs/promises");
|
|
7
8
|
const node_zlib_1 = require("node:zlib");
|
|
8
9
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
9
10
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
@@ -20,7 +21,7 @@ class LocalFilePersistencePlugin {
|
|
|
20
21
|
}
|
|
21
22
|
async ping() { }
|
|
22
23
|
async getTables() {
|
|
23
|
-
return (await
|
|
24
|
+
return (await promises_1.default.readdir(this.cfg.storagePath))
|
|
24
25
|
.filter(f => f.includes('.ndjson'))
|
|
25
26
|
.map(f => f.split('.ndjson')[0]);
|
|
26
27
|
}
|
|
@@ -33,7 +34,7 @@ class LocalFilePersistencePlugin {
|
|
|
33
34
|
const transformUnzip = this.cfg.gzip ? [(0, node_zlib_1.createUnzip)()] : [];
|
|
34
35
|
const rows = [];
|
|
35
36
|
await (0, nodejs_lib_1._pipeline)([
|
|
36
|
-
|
|
37
|
+
node_fs_1.default.createReadStream(filePath),
|
|
37
38
|
...transformUnzip,
|
|
38
39
|
(0, nodejs_lib_1.transformSplit)(),
|
|
39
40
|
(0, nodejs_lib_1.transformJsonParse)(),
|
|
@@ -53,7 +54,7 @@ class LocalFilePersistencePlugin {
|
|
|
53
54
|
node_stream_1.Readable.from(rows),
|
|
54
55
|
(0, nodejs_lib_1.transformToNDJson)(),
|
|
55
56
|
...transformZip,
|
|
56
|
-
|
|
57
|
+
node_fs_1.default.createWriteStream(filePath),
|
|
57
58
|
]);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.InMemoryDB = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
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"));
|
|
6
7
|
const node_stream_1 = require("node:stream");
|
|
7
8
|
const node_zlib_1 = require("node:zlib");
|
|
8
9
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
9
10
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
10
|
-
const nodejs_lib_2 = require("@naturalcycles/nodejs-lib");
|
|
11
11
|
const __1 = require("../..");
|
|
12
12
|
const dbQuery_1 = require("../../query/dbQuery");
|
|
13
13
|
class InMemoryDB {
|
|
@@ -181,10 +181,10 @@ class InMemoryDB {
|
|
|
181
181
|
node_stream_1.Readable.from(rows),
|
|
182
182
|
(0, nodejs_lib_1.transformToNDJson)(),
|
|
183
183
|
...transformZip,
|
|
184
|
-
|
|
184
|
+
node_fs_1.default.createWriteStream(fname),
|
|
185
185
|
]);
|
|
186
186
|
});
|
|
187
|
-
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`);
|
|
188
188
|
}
|
|
189
189
|
/**
|
|
190
190
|
* Restores all tables (all namespaces) at once.
|
|
@@ -195,7 +195,7 @@ class InMemoryDB {
|
|
|
195
195
|
const started = Date.now();
|
|
196
196
|
await (0, nodejs_lib_1._ensureDir)(persistentStoragePath);
|
|
197
197
|
this.data = {}; // empty it in the beginning!
|
|
198
|
-
const files = (await
|
|
198
|
+
const files = (await promises_1.default.readdir(persistentStoragePath)).filter(f => f.includes('.ndjson'));
|
|
199
199
|
// infinite concurrency for now
|
|
200
200
|
await (0, js_lib_1.pMap)(files, async (file) => {
|
|
201
201
|
const fname = `${persistentStoragePath}/${file}`;
|
|
@@ -203,7 +203,7 @@ class InMemoryDB {
|
|
|
203
203
|
const transformUnzip = file.endsWith('.gz') ? [(0, node_zlib_1.createUnzip)()] : [];
|
|
204
204
|
const rows = [];
|
|
205
205
|
await (0, nodejs_lib_1._pipeline)([
|
|
206
|
-
|
|
206
|
+
node_fs_1.default.createReadStream(fname),
|
|
207
207
|
...transformUnzip,
|
|
208
208
|
(0, nodejs_lib_1.transformSplit)(),
|
|
209
209
|
(0, nodejs_lib_1.transformJsonParse)(),
|
|
@@ -211,7 +211,7 @@ class InMemoryDB {
|
|
|
211
211
|
]);
|
|
212
212
|
this.data[table] = (0, js_lib_1._by)(rows, r => r.id);
|
|
213
213
|
});
|
|
214
|
-
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`);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
exports.InMemoryDB = InMemoryDB;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Transform } from 'node:stream';
|
|
1
3
|
import { AnyObject, AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Promisable, Saved, UnixTimestampMillisNumber, Unsaved, ZodSchema } from '@naturalcycles/js-lib';
|
|
2
4
|
import { AjvSchema, ObjectSchema, ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
5
|
import { DBDeleteByIdsOperation, DBModelType, DBOperation, DBPatch, DBSaveBatchOperation, RunQueryResult } from '../db.model';
|
|
4
6
|
import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
|
|
5
|
-
import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions } from './common.dao.model';
|
|
7
|
+
import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model';
|
|
6
8
|
/**
|
|
7
9
|
* Lowest common denominator API between supported Databases.
|
|
8
10
|
*
|
|
@@ -62,7 +64,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
62
64
|
/**
|
|
63
65
|
* Stream as Readable, to be able to .pipe() it further with support of backpressure.
|
|
64
66
|
*/
|
|
65
|
-
streamQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions): ReadableTyped<DBM>;
|
|
67
|
+
streamQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<DBM>): ReadableTyped<DBM>;
|
|
66
68
|
/**
|
|
67
69
|
* Stream as Readable, to be able to .pipe() it further with support of backpressure.
|
|
68
70
|
*
|
|
@@ -72,9 +74,9 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
72
74
|
*
|
|
73
75
|
* You can do `.pipe(transformNoOp)` to make it "valid again".
|
|
74
76
|
*/
|
|
75
|
-
streamQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions): ReadableTyped<Saved<BM>>;
|
|
77
|
+
streamQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<Saved<BM>>): ReadableTyped<Saved<BM>>;
|
|
76
78
|
queryIds(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<ID[]>;
|
|
77
|
-
streamQueryIds(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions): ReadableTyped<ID>;
|
|
79
|
+
streamQueryIds(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<ID>): ReadableTyped<ID>;
|
|
78
80
|
streamQueryIdsForEach(q: DBQuery<DBM>, mapper: AsyncMapper<ID, void>, opt?: CommonDaoStreamForEachOptions<ID>): Promise<void>;
|
|
79
81
|
/**
|
|
80
82
|
* Mutates!
|
|
@@ -106,6 +108,13 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
106
108
|
saveAsDBM(dbm: DBM, opt?: CommonDaoSaveOptions<DBM>): Promise<DBM>;
|
|
107
109
|
saveBatch(bms: Unsaved<BM>[], opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>[]>;
|
|
108
110
|
saveBatchAsDBM(dbms: DBM[], opt?: CommonDaoSaveOptions<DBM>): Promise<DBM[]>;
|
|
111
|
+
/**
|
|
112
|
+
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
113
|
+
* (of size opt.batchSize, which defaults to 500),
|
|
114
|
+
* and then executing db.saveBatch(batch) with the concurrency
|
|
115
|
+
* of opt.batchConcurrency (which defaults to 16).
|
|
116
|
+
*/
|
|
117
|
+
streamSaveTransform(opt?: CommonDaoStreamSaveOptions<DBM>): Transform[];
|
|
109
118
|
/**
|
|
110
119
|
* @returns number of deleted items
|
|
111
120
|
*/
|
|
@@ -117,9 +126,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
117
126
|
* `deleteByIds` for each batch concurrently (infinite concurrency).
|
|
118
127
|
* This is expected to be more memory-efficient way of deleting big numbers of rows.
|
|
119
128
|
*/
|
|
120
|
-
deleteByQuery(q: DBQuery<DBM>, opt?:
|
|
121
|
-
stream?: boolean;
|
|
122
|
-
}): Promise<number>;
|
|
129
|
+
deleteByQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamDeleteOptions<DBM>): Promise<number>;
|
|
123
130
|
updateById(id: ID, patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
|
|
124
131
|
updateByIds(ids: ID[], patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
|
|
125
132
|
updateByQuery(q: DBQuery<DBM>, patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
|
|
@@ -718,6 +718,58 @@ class CommonDao {
|
|
|
718
718
|
this.logSaveResult(started, op, table);
|
|
719
719
|
return rows;
|
|
720
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
723
|
+
* (of size opt.batchSize, which defaults to 500),
|
|
724
|
+
* and then executing db.saveBatch(batch) with the concurrency
|
|
725
|
+
* of opt.batchConcurrency (which defaults to 16).
|
|
726
|
+
*/
|
|
727
|
+
streamSaveTransform(opt = {}) {
|
|
728
|
+
this.requireWriteAccess();
|
|
729
|
+
const table = opt.table || this.cfg.table;
|
|
730
|
+
opt.skipValidation ??= true;
|
|
731
|
+
opt.skipConversion ??= true;
|
|
732
|
+
opt.errorMode ||= js_lib_1.ErrorMode.SUPPRESS;
|
|
733
|
+
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
734
|
+
opt = { ...opt, saveMethod: 'insert' };
|
|
735
|
+
}
|
|
736
|
+
const excludeFromIndexes = opt.excludeFromIndexes || this.cfg.excludeFromIndexes;
|
|
737
|
+
const { beforeSave } = this.cfg.hooks;
|
|
738
|
+
const { batchSize = 500, batchConcurrency = 16, errorMode } = opt;
|
|
739
|
+
return [
|
|
740
|
+
(0, nodejs_lib_1.transformMap)(async (bm) => {
|
|
741
|
+
this.assignIdCreatedUpdated(bm, opt); // mutates
|
|
742
|
+
let dbm = await this.bmToDBM(bm, opt);
|
|
743
|
+
if (beforeSave) {
|
|
744
|
+
dbm = (await beforeSave(dbm));
|
|
745
|
+
if (dbm === null && !opt.tx)
|
|
746
|
+
return js_lib_1.SKIP;
|
|
747
|
+
}
|
|
748
|
+
return dbm;
|
|
749
|
+
}, {
|
|
750
|
+
errorMode,
|
|
751
|
+
}),
|
|
752
|
+
(0, nodejs_lib_1.transformBuffer)({ batchSize }),
|
|
753
|
+
(0, nodejs_lib_1.transformMap)(async (batch) => {
|
|
754
|
+
await this.cfg.db.saveBatch(table, batch, {
|
|
755
|
+
...opt,
|
|
756
|
+
excludeFromIndexes,
|
|
757
|
+
});
|
|
758
|
+
return batch;
|
|
759
|
+
}, {
|
|
760
|
+
concurrency: batchConcurrency,
|
|
761
|
+
errorMode,
|
|
762
|
+
flattenArrayOutput: true,
|
|
763
|
+
}),
|
|
764
|
+
(0, nodejs_lib_1.transformLogProgress)({
|
|
765
|
+
metric: 'saved',
|
|
766
|
+
...opt,
|
|
767
|
+
}),
|
|
768
|
+
// just to satisfy and simplify typings
|
|
769
|
+
// It's easier to return Transform[], rather than (Transform | Writable)[]
|
|
770
|
+
(0, nodejs_lib_1.writableVoid)(),
|
|
771
|
+
];
|
|
772
|
+
}
|
|
721
773
|
async deleteById(id, opt = {}) {
|
|
722
774
|
if (!id)
|
|
723
775
|
return 0;
|
|
@@ -754,8 +806,8 @@ class CommonDao {
|
|
|
754
806
|
const op = `deleteByQuery(${q.pretty()})`;
|
|
755
807
|
const started = this.logStarted(op, q.table);
|
|
756
808
|
let deleted = 0;
|
|
757
|
-
if (opt.
|
|
758
|
-
const batchSize =
|
|
809
|
+
if (opt.batchSize) {
|
|
810
|
+
const { batchSize, batchConcurrency = 16 } = opt;
|
|
759
811
|
await (0, nodejs_lib_1._pipeline)([
|
|
760
812
|
this.cfg.db.streamQuery(q.select(['id']), opt),
|
|
761
813
|
(0, nodejs_lib_1.transformMapSimple)(objectWithId => objectWithId.id, {
|
|
@@ -766,6 +818,7 @@ class CommonDao {
|
|
|
766
818
|
deleted += await this.cfg.db.deleteByQuery(dbQuery_1.DBQuery.create(q.table).filterIn('id', ids), opt);
|
|
767
819
|
}, {
|
|
768
820
|
predicate: js_lib_1._passthroughPredicate,
|
|
821
|
+
concurrency: batchConcurrency,
|
|
769
822
|
}),
|
|
770
823
|
// LogProgress should be AFTER the mapper, to be able to report correct stats
|
|
771
824
|
(0, nodejs_lib_1.transformLogProgress)({
|
|
@@ -252,9 +252,13 @@ export interface CommonDaoSaveOptions<DBM extends ObjectWithId> extends CommonDa
|
|
|
252
252
|
*/
|
|
253
253
|
ensureUniqueId?: boolean;
|
|
254
254
|
}
|
|
255
|
-
export interface
|
|
255
|
+
export interface CommonDaoStreamDeleteOptions<DBM extends ObjectWithId> extends CommonDaoStreamOptions<DBM> {
|
|
256
256
|
}
|
|
257
|
-
export interface
|
|
257
|
+
export interface CommonDaoStreamSaveOptions<DBM extends ObjectWithId> extends CommonDaoSaveOptions<DBM>, CommonDaoStreamOptions<DBM> {
|
|
258
|
+
}
|
|
259
|
+
export interface CommonDaoStreamForEachOptions<IN> extends CommonDaoStreamOptions<IN>, TransformMapOptions<IN, any> {
|
|
260
|
+
}
|
|
261
|
+
export interface CommonDaoStreamOptions<IN> extends CommonDaoOptions, TransformLogProgressOptions<IN> {
|
|
258
262
|
/**
|
|
259
263
|
* @default true (for streams)
|
|
260
264
|
*/
|
|
@@ -268,5 +272,17 @@ export interface CommonDaoStreamOptions extends CommonDaoOptions {
|
|
|
268
272
|
* @default ErrorMode.SUPPRESS for .forEach() streams as well, but overridable
|
|
269
273
|
*/
|
|
270
274
|
errorMode?: ErrorMode;
|
|
275
|
+
/**
|
|
276
|
+
* Applicable to some of stream operations, e.g deleteByQuery.
|
|
277
|
+
* If set - `deleteByQuery` won't execute it "all at once", but in batches.
|
|
278
|
+
*
|
|
279
|
+
* Defaults to undefined, so the operation is executed "all at once".
|
|
280
|
+
*/
|
|
281
|
+
batchSize?: number;
|
|
282
|
+
/**
|
|
283
|
+
* When batchSize is set - this option controls how many batches to run concurrently.
|
|
284
|
+
* Defaults to 16, "the magic number of JavaScript concurrency".
|
|
285
|
+
*/
|
|
286
|
+
batchConcurrency?: number;
|
|
271
287
|
}
|
|
272
288
|
export type CommonDaoCreateOptions = CommonDBCreateOptions;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dbPipelineBackup = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
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"));
|
|
6
7
|
const node_zlib_1 = require("node:zlib");
|
|
7
8
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
8
9
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
9
|
-
const nodejs_lib_2 = require("@naturalcycles/nodejs-lib");
|
|
10
10
|
const index_1 = require("../index");
|
|
11
11
|
/**
|
|
12
12
|
* Pipeline from input stream(s) to a NDJSON file (optionally gzipped).
|
|
@@ -22,11 +22,11 @@ async function dbPipelineBackup(opt) {
|
|
|
22
22
|
const strict = errorMode !== js_lib_1.ErrorMode.SUPPRESS;
|
|
23
23
|
const gzip = opt.gzip !== false; // default to true
|
|
24
24
|
let { tables } = opt;
|
|
25
|
-
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0,
|
|
26
|
-
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}`);
|
|
27
27
|
(0, nodejs_lib_1._ensureDirSync)(outputDirPath);
|
|
28
28
|
tables ||= await db.getTables();
|
|
29
|
-
console.log(`${(0,
|
|
29
|
+
console.log(`${(0, nodejs_lib_1.yellow)(tables.length)} ${(0, nodejs_lib_1.boldWhite)('table(s)')}:\n` + tables.join('\n'));
|
|
30
30
|
const statsPerTable = {};
|
|
31
31
|
await (0, js_lib_1.pMap)(tables, async (table) => {
|
|
32
32
|
let q = index_1.DBQuery.create(table).limit(limit);
|
|
@@ -41,11 +41,11 @@ async function dbPipelineBackup(opt) {
|
|
|
41
41
|
const started = Date.now();
|
|
42
42
|
let rows = 0;
|
|
43
43
|
(0, nodejs_lib_1._ensureFileSync)(filePath);
|
|
44
|
-
console.log(`>> ${(0,
|
|
44
|
+
console.log(`>> ${(0, nodejs_lib_1.grey)(filePath)} started...`);
|
|
45
45
|
if (emitSchemaFromDB) {
|
|
46
46
|
const schema = await db.getTableSchema(table);
|
|
47
47
|
await (0, nodejs_lib_1._writeJson)(schemaFilePath, schema, { spaces: 2 });
|
|
48
|
-
console.log(`>> ${(0,
|
|
48
|
+
console.log(`>> ${(0, nodejs_lib_1.grey)(schemaFilePath)} saved (generated from DB)`);
|
|
49
49
|
}
|
|
50
50
|
await (0, nodejs_lib_1._pipeline)([
|
|
51
51
|
db.streamQuery(q),
|
|
@@ -65,15 +65,15 @@ async function dbPipelineBackup(opt) {
|
|
|
65
65
|
}),
|
|
66
66
|
(0, nodejs_lib_1.transformToNDJson)({ strict, sortObjects }),
|
|
67
67
|
...(gzip ? [(0, node_zlib_1.createGzip)(zlibOptions)] : []),
|
|
68
|
-
|
|
68
|
+
node_fs_1.default.createWriteStream(filePath),
|
|
69
69
|
]);
|
|
70
|
-
const { size: sizeBytes } = await
|
|
70
|
+
const { size: sizeBytes } = await promises_1.default.stat(filePath);
|
|
71
71
|
const stats = nodejs_lib_1.NDJsonStats.create({
|
|
72
72
|
tookMillis: Date.now() - started,
|
|
73
73
|
rows,
|
|
74
74
|
sizeBytes,
|
|
75
75
|
});
|
|
76
|
-
console.log(`>> ${(0,
|
|
76
|
+
console.log(`>> ${(0, nodejs_lib_1.grey)(filePath)}\n` + stats.toPretty());
|
|
77
77
|
statsPerTable[table] = stats;
|
|
78
78
|
}, { concurrency, errorMode });
|
|
79
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));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dbPipelineRestore = void 0;
|
|
4
|
-
const
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
5
6
|
const node_zlib_1 = require("node:zlib");
|
|
6
7
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
7
8
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
8
|
-
const nodejs_lib_2 = require("@naturalcycles/nodejs-lib");
|
|
9
9
|
/**
|
|
10
10
|
* Pipeline from NDJSON files in a folder (optionally gzipped) to CommonDB.
|
|
11
11
|
* Allows to define a mapper and a predicate to map/filter objects between input and output.
|
|
@@ -17,14 +17,14 @@ async function dbPipelineRestore(opt) {
|
|
|
17
17
|
const { db, concurrency = 16, batchSize = 100, limit, sinceUpdated, inputDirPath, mapperPerTable = {}, saveOptionsPerTable = {}, transformMapOptions, errorMode = js_lib_1.ErrorMode.SUPPRESS, recreateTables = false, } = opt;
|
|
18
18
|
const strict = errorMode !== js_lib_1.ErrorMode.SUPPRESS;
|
|
19
19
|
const onlyTables = opt.tables && new Set(opt.tables);
|
|
20
|
-
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0,
|
|
21
|
-
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}`);
|
|
22
22
|
(0, nodejs_lib_1._ensureDirSync)(inputDirPath);
|
|
23
23
|
const tablesToGzip = new Set();
|
|
24
24
|
const sizeByTable = {};
|
|
25
25
|
const statsPerTable = {};
|
|
26
26
|
const tables = [];
|
|
27
|
-
|
|
27
|
+
node_fs_1.default.readdirSync(inputDirPath).forEach(f => {
|
|
28
28
|
let table;
|
|
29
29
|
let gzip = false;
|
|
30
30
|
if (f.endsWith('.ndjson')) {
|
|
@@ -42,15 +42,15 @@ async function dbPipelineRestore(opt) {
|
|
|
42
42
|
tables.push(table);
|
|
43
43
|
if (gzip)
|
|
44
44
|
tablesToGzip.add(table);
|
|
45
|
-
sizeByTable[table] =
|
|
45
|
+
sizeByTable[table] = node_fs_1.default.statSync(`${inputDirPath}/${f}`).size;
|
|
46
46
|
});
|
|
47
47
|
const sizeStrByTable = (0, js_lib_1._mapValues)(sizeByTable, (_k, b) => (0, js_lib_1._hb)(b));
|
|
48
|
-
console.log(`${(0,
|
|
48
|
+
console.log(`${(0, nodejs_lib_1.yellow)(tables.length)} ${(0, nodejs_lib_1.boldWhite)('table(s)')}:\n`, sizeStrByTable);
|
|
49
49
|
// const schemaByTable: Record<string, CommonSchema> = {}
|
|
50
50
|
if (recreateTables) {
|
|
51
51
|
await (0, js_lib_1.pMap)(tables, async (table) => {
|
|
52
52
|
const schemaFilePath = `${inputDirPath}/${table}.schema.json`;
|
|
53
|
-
if (!
|
|
53
|
+
if (!node_fs_1.default.existsSync(schemaFilePath)) {
|
|
54
54
|
console.warn(`${schemaFilePath} does not exist!`);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
@@ -65,9 +65,9 @@ async function dbPipelineRestore(opt) {
|
|
|
65
65
|
const started = Date.now();
|
|
66
66
|
let rows = 0;
|
|
67
67
|
const sizeBytes = sizeByTable[table];
|
|
68
|
-
console.log(`<< ${(0,
|
|
68
|
+
console.log(`<< ${(0, nodejs_lib_1.grey)(filePath)} ${(0, nodejs_lib_1.dimWhite)((0, js_lib_1._hb)(sizeBytes))} started...`);
|
|
69
69
|
await (0, nodejs_lib_1._pipeline)([
|
|
70
|
-
|
|
70
|
+
node_fs_1.default.createReadStream(filePath),
|
|
71
71
|
...(gzip ? [(0, node_zlib_1.createUnzip)()] : []),
|
|
72
72
|
(0, nodejs_lib_1.transformSplit)(),
|
|
73
73
|
(0, nodejs_lib_1.transformJsonParse)({ strict }),
|
|
@@ -97,7 +97,7 @@ async function dbPipelineRestore(opt) {
|
|
|
97
97
|
rows,
|
|
98
98
|
sizeBytes,
|
|
99
99
|
});
|
|
100
|
-
console.log(`<< ${(0,
|
|
100
|
+
console.log(`<< ${(0, nodejs_lib_1.grey)(filePath)}\n` + stats.toPretty());
|
|
101
101
|
statsPerTable[table] = stats;
|
|
102
102
|
}, { concurrency, errorMode });
|
|
103
103
|
const statsTotal = nodejs_lib_1.NDJsonStats.createCombined(Object.values(statsPerTable));
|
package/dist/query/dbQuery.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnyObjectWithId, ObjectWithId, AsyncMapper, Saved, AnyObject } from '@naturalcycles/js-lib';
|
|
2
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
|
-
import { CommonDaoOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, DBPatch } from '..';
|
|
3
|
+
import { CommonDaoOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, DBPatch } from '..';
|
|
4
4
|
import { CommonDao } from '../commondao/common.dao';
|
|
5
5
|
import { RunQueryResult } from '../db.model';
|
|
6
6
|
/**
|
|
@@ -101,12 +101,10 @@ export declare class RunnableDBQuery<BM extends Partial<ObjectWithId<ID>>, DBM e
|
|
|
101
101
|
updateByQuery(patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
|
|
102
102
|
streamQueryForEach(mapper: AsyncMapper<Saved<BM>, void>, opt?: CommonDaoStreamForEachOptions<Saved<BM>>): Promise<void>;
|
|
103
103
|
streamQueryAsDBMForEach(mapper: AsyncMapper<DBM, void>, opt?: CommonDaoStreamForEachOptions<DBM>): Promise<void>;
|
|
104
|
-
streamQuery(opt?: CommonDaoStreamOptions): ReadableTyped<Saved<BM>>;
|
|
105
|
-
streamQueryAsDBM(opt?: CommonDaoStreamOptions): ReadableTyped<DBM>;
|
|
104
|
+
streamQuery(opt?: CommonDaoStreamOptions<Saved<BM>>): ReadableTyped<Saved<BM>>;
|
|
105
|
+
streamQueryAsDBM(opt?: CommonDaoStreamOptions<DBM>): ReadableTyped<DBM>;
|
|
106
106
|
queryIds(opt?: CommonDaoOptions): Promise<ID[]>;
|
|
107
|
-
streamQueryIds(opt?: CommonDaoStreamOptions): ReadableTyped<ID>;
|
|
107
|
+
streamQueryIds(opt?: CommonDaoStreamOptions<ID>): ReadableTyped<ID>;
|
|
108
108
|
streamQueryIdsForEach(mapper: AsyncMapper<ID, void>, opt?: CommonDaoStreamForEachOptions<ID>): Promise<void>;
|
|
109
|
-
deleteByQuery(opt?:
|
|
110
|
-
stream?: boolean;
|
|
111
|
-
}): Promise<number>;
|
|
109
|
+
deleteByQuery(opt?: CommonDaoStreamDeleteOptions<DBM>): Promise<number>;
|
|
112
110
|
}
|
package/dist/testing/daoTest.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runCommonDaoTest = void 0;
|
|
4
|
+
const node_stream_1 = require("node:stream");
|
|
4
5
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
6
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
7
|
const __1 = require("..");
|
|
@@ -175,6 +176,15 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
|
|
|
175
176
|
ids = ids.sort();
|
|
176
177
|
(0, dbTest_1.expectMatch)(expectedItems.map(i => i.id), ids, quirks);
|
|
177
178
|
});
|
|
179
|
+
test('streamSaveTransform', async () => {
|
|
180
|
+
const items2 = (0, test_model_1.createTestItemsBM)(2).map(i => ({ ...i, id: i.id + '_str' }));
|
|
181
|
+
const ids = items2.map(i => i.id);
|
|
182
|
+
await (0, nodejs_lib_1._pipeline)([node_stream_1.Readable.from(items2), ...dao.streamSaveTransform()]);
|
|
183
|
+
const items2Loaded = await dao.getByIds(ids);
|
|
184
|
+
(0, dbTest_1.expectMatch)(items2, items2Loaded, quirks);
|
|
185
|
+
// cleanup
|
|
186
|
+
await dao.query().filterIn('id', ids).deleteByQuery();
|
|
187
|
+
});
|
|
178
188
|
}
|
|
179
189
|
// DELETE BY
|
|
180
190
|
if (querying) {
|
|
@@ -195,7 +205,7 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
|
|
|
195
205
|
// cleanup
|
|
196
206
|
await dao.query().deleteByQuery();
|
|
197
207
|
// Test that id, created, updated are created
|
|
198
|
-
const now = (0, js_lib_1.
|
|
208
|
+
const now = (0, js_lib_1.localTimeNow)().unix();
|
|
199
209
|
await dao.runInTransaction([dao.tx.save((0, js_lib_1._omit)(item1, ['id', 'created', 'updated']))]);
|
|
200
210
|
const loaded = await dao.query().runQuery();
|
|
201
211
|
expect(loaded.length).toBe(1);
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@naturalcycles/js-lib": "^14.116.0",
|
|
8
|
-
"@naturalcycles/nodejs-lib": "^
|
|
8
|
+
"@naturalcycles/nodejs-lib": "^13.1.1"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@naturalcycles/bench-lib": "^1.0.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=18.12"
|
|
42
42
|
},
|
|
43
|
-
"version": "8.
|
|
43
|
+
"version": "8.55.1",
|
|
44
44
|
"description": "Lowest Common Denominator API to supported Databases",
|
|
45
45
|
"keywords": [
|
|
46
46
|
"db",
|
|
@@ -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
|
-
import
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import fsp from 'node:fs/promises'
|
|
2
3
|
import { Readable } from 'node:stream'
|
|
3
|
-
import * as fsp from 'node:fs/promises'
|
|
4
4
|
import { createGzip, createUnzip } from 'node:zlib'
|
|
5
5
|
import { pMap, ObjectWithId } from '@naturalcycles/js-lib'
|
|
6
6
|
import {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import fsp from 'node:fs/promises'
|
|
3
3
|
import { Readable } from 'node:stream'
|
|
4
4
|
import { createGzip, createUnzip } from 'node:zlib'
|
|
5
5
|
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,
|
|
@@ -316,12 +316,20 @@ export interface CommonDaoSaveOptions<DBM extends ObjectWithId>
|
|
|
316
316
|
ensureUniqueId?: boolean
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
export interface CommonDaoStreamDeleteOptions<DBM extends ObjectWithId>
|
|
320
|
+
extends CommonDaoStreamOptions<DBM> {}
|
|
321
|
+
|
|
322
|
+
export interface CommonDaoStreamSaveOptions<DBM extends ObjectWithId>
|
|
323
|
+
extends CommonDaoSaveOptions<DBM>,
|
|
324
|
+
CommonDaoStreamOptions<DBM> {}
|
|
325
|
+
|
|
319
326
|
export interface CommonDaoStreamForEachOptions<IN>
|
|
320
|
-
extends CommonDaoStreamOptions
|
|
321
|
-
TransformMapOptions<IN, any
|
|
322
|
-
TransformLogProgressOptions<IN> {}
|
|
327
|
+
extends CommonDaoStreamOptions<IN>,
|
|
328
|
+
TransformMapOptions<IN, any> {}
|
|
323
329
|
|
|
324
|
-
export interface CommonDaoStreamOptions
|
|
330
|
+
export interface CommonDaoStreamOptions<IN>
|
|
331
|
+
extends CommonDaoOptions,
|
|
332
|
+
TransformLogProgressOptions<IN> {
|
|
325
333
|
/**
|
|
326
334
|
* @default true (for streams)
|
|
327
335
|
*/
|
|
@@ -337,6 +345,20 @@ export interface CommonDaoStreamOptions extends CommonDaoOptions {
|
|
|
337
345
|
* @default ErrorMode.SUPPRESS for .forEach() streams as well, but overridable
|
|
338
346
|
*/
|
|
339
347
|
errorMode?: ErrorMode
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Applicable to some of stream operations, e.g deleteByQuery.
|
|
351
|
+
* If set - `deleteByQuery` won't execute it "all at once", but in batches.
|
|
352
|
+
*
|
|
353
|
+
* Defaults to undefined, so the operation is executed "all at once".
|
|
354
|
+
*/
|
|
355
|
+
batchSize?: number
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* When batchSize is set - this option controls how many batches to run concurrently.
|
|
359
|
+
* Defaults to 16, "the magic number of JavaScript concurrency".
|
|
360
|
+
*/
|
|
361
|
+
batchConcurrency?: number
|
|
340
362
|
}
|
|
341
363
|
|
|
342
364
|
export type CommonDaoCreateOptions = CommonDBCreateOptions
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Transform } from 'node:stream'
|
|
1
2
|
import {
|
|
2
3
|
_assert,
|
|
3
4
|
_filterNullishValues,
|
|
@@ -57,8 +58,10 @@ import {
|
|
|
57
58
|
CommonDaoLogLevel,
|
|
58
59
|
CommonDaoOptions,
|
|
59
60
|
CommonDaoSaveOptions,
|
|
61
|
+
CommonDaoStreamDeleteOptions,
|
|
60
62
|
CommonDaoStreamForEachOptions,
|
|
61
63
|
CommonDaoStreamOptions,
|
|
64
|
+
CommonDaoStreamSaveOptions,
|
|
62
65
|
} from './common.dao.model'
|
|
63
66
|
|
|
64
67
|
const isGAE = !!process.env['GAE_INSTANCE']
|
|
@@ -529,7 +532,7 @@ export class CommonDao<
|
|
|
529
532
|
/**
|
|
530
533
|
* Stream as Readable, to be able to .pipe() it further with support of backpressure.
|
|
531
534
|
*/
|
|
532
|
-
streamQueryAsDBM(q: DBQuery<DBM>, opt: CommonDaoStreamOptions = {}): ReadableTyped<DBM> {
|
|
535
|
+
streamQueryAsDBM(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<DBM> = {}): ReadableTyped<DBM> {
|
|
533
536
|
q.table = opt.table || q.table
|
|
534
537
|
opt.skipValidation = opt.skipValidation !== false // default true
|
|
535
538
|
opt.skipConversion = opt.skipConversion !== false // default true
|
|
@@ -568,7 +571,10 @@ export class CommonDao<
|
|
|
568
571
|
*
|
|
569
572
|
* You can do `.pipe(transformNoOp)` to make it "valid again".
|
|
570
573
|
*/
|
|
571
|
-
streamQuery(
|
|
574
|
+
streamQuery(
|
|
575
|
+
q: DBQuery<DBM>,
|
|
576
|
+
opt: CommonDaoStreamOptions<Saved<BM>> = {},
|
|
577
|
+
): ReadableTyped<Saved<BM>> {
|
|
572
578
|
q.table = opt.table || q.table
|
|
573
579
|
opt.skipValidation = opt.skipValidation !== false // default true
|
|
574
580
|
opt.skipConversion = opt.skipConversion !== false // default true
|
|
@@ -611,7 +617,7 @@ export class CommonDao<
|
|
|
611
617
|
return rows.map(r => r.id)
|
|
612
618
|
}
|
|
613
619
|
|
|
614
|
-
streamQueryIds(q: DBQuery<DBM>, opt: CommonDaoStreamOptions = {}): ReadableTyped<ID> {
|
|
620
|
+
streamQueryIds(q: DBQuery<DBM>, opt: CommonDaoStreamOptions<ID> = {}): ReadableTyped<ID> {
|
|
615
621
|
q.table = opt.table || q.table
|
|
616
622
|
opt.errorMode ||= ErrorMode.SUPPRESS
|
|
617
623
|
|
|
@@ -958,6 +964,72 @@ export class CommonDao<
|
|
|
958
964
|
return rows
|
|
959
965
|
}
|
|
960
966
|
|
|
967
|
+
/**
|
|
968
|
+
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
969
|
+
* (of size opt.batchSize, which defaults to 500),
|
|
970
|
+
* and then executing db.saveBatch(batch) with the concurrency
|
|
971
|
+
* of opt.batchConcurrency (which defaults to 16).
|
|
972
|
+
*/
|
|
973
|
+
streamSaveTransform(opt: CommonDaoStreamSaveOptions<DBM> = {}): Transform[] {
|
|
974
|
+
this.requireWriteAccess()
|
|
975
|
+
|
|
976
|
+
const table = opt.table || this.cfg.table
|
|
977
|
+
opt.skipValidation ??= true
|
|
978
|
+
opt.skipConversion ??= true
|
|
979
|
+
opt.errorMode ||= ErrorMode.SUPPRESS
|
|
980
|
+
|
|
981
|
+
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
982
|
+
opt = { ...opt, saveMethod: 'insert' }
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const excludeFromIndexes = opt.excludeFromIndexes || this.cfg.excludeFromIndexes
|
|
986
|
+
const { beforeSave } = this.cfg.hooks!
|
|
987
|
+
|
|
988
|
+
const { batchSize = 500, batchConcurrency = 16, errorMode } = opt
|
|
989
|
+
|
|
990
|
+
return [
|
|
991
|
+
transformMap<BM, DBM>(
|
|
992
|
+
async bm => {
|
|
993
|
+
this.assignIdCreatedUpdated(bm, opt) // mutates
|
|
994
|
+
|
|
995
|
+
let dbm = await this.bmToDBM(bm, opt)
|
|
996
|
+
|
|
997
|
+
if (beforeSave) {
|
|
998
|
+
dbm = (await beforeSave(dbm))!
|
|
999
|
+
if (dbm === null && !opt.tx) return SKIP
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
return dbm
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
errorMode,
|
|
1006
|
+
},
|
|
1007
|
+
),
|
|
1008
|
+
transformBuffer<DBM>({ batchSize }),
|
|
1009
|
+
transformMap<DBM[], DBM[]>(
|
|
1010
|
+
async batch => {
|
|
1011
|
+
await this.cfg.db.saveBatch(table, batch, {
|
|
1012
|
+
...opt,
|
|
1013
|
+
excludeFromIndexes,
|
|
1014
|
+
})
|
|
1015
|
+
return batch
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
concurrency: batchConcurrency,
|
|
1019
|
+
errorMode,
|
|
1020
|
+
flattenArrayOutput: true,
|
|
1021
|
+
},
|
|
1022
|
+
),
|
|
1023
|
+
transformLogProgress({
|
|
1024
|
+
metric: 'saved',
|
|
1025
|
+
...opt,
|
|
1026
|
+
}),
|
|
1027
|
+
// just to satisfy and simplify typings
|
|
1028
|
+
// It's easier to return Transform[], rather than (Transform | Writable)[]
|
|
1029
|
+
writableVoid() as Transform,
|
|
1030
|
+
]
|
|
1031
|
+
}
|
|
1032
|
+
|
|
961
1033
|
// DELETE
|
|
962
1034
|
/**
|
|
963
1035
|
* @returns number of deleted items
|
|
@@ -995,7 +1067,7 @@ export class CommonDao<
|
|
|
995
1067
|
*/
|
|
996
1068
|
async deleteByQuery(
|
|
997
1069
|
q: DBQuery<DBM>,
|
|
998
|
-
opt:
|
|
1070
|
+
opt: CommonDaoStreamDeleteOptions<DBM> = {},
|
|
999
1071
|
): Promise<number> {
|
|
1000
1072
|
this.requireWriteAccess()
|
|
1001
1073
|
this.requireObjectMutability(opt)
|
|
@@ -1004,8 +1076,8 @@ export class CommonDao<
|
|
|
1004
1076
|
const started = this.logStarted(op, q.table)
|
|
1005
1077
|
let deleted = 0
|
|
1006
1078
|
|
|
1007
|
-
if (opt.
|
|
1008
|
-
const batchSize =
|
|
1079
|
+
if (opt.batchSize) {
|
|
1080
|
+
const { batchSize, batchConcurrency = 16 } = opt
|
|
1009
1081
|
|
|
1010
1082
|
await _pipeline([
|
|
1011
1083
|
this.cfg.db.streamQuery<DBM>(q.select(['id']), opt),
|
|
@@ -1022,6 +1094,7 @@ export class CommonDao<
|
|
|
1022
1094
|
},
|
|
1023
1095
|
{
|
|
1024
1096
|
predicate: _passthroughPredicate,
|
|
1097
|
+
concurrency: batchConcurrency,
|
|
1025
1098
|
},
|
|
1026
1099
|
),
|
|
1027
1100
|
// LogProgress should be AFTER the mapper, to be able to report correct stats
|
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,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import fsp from 'node:fs/promises'
|
|
3
3
|
import { createGzip, ZlibOptions } from 'node:zlib'
|
|
4
4
|
import {
|
|
5
5
|
AppError,
|
|
@@ -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'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs'
|
|
2
2
|
import { createUnzip } from 'node:zlib'
|
|
3
3
|
import {
|
|
4
4
|
AsyncMapper,
|
|
@@ -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/query/dbQuery.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
11
11
|
import {
|
|
12
12
|
CommonDaoOptions,
|
|
13
|
+
CommonDaoStreamDeleteOptions,
|
|
13
14
|
CommonDaoStreamForEachOptions,
|
|
14
15
|
CommonDaoStreamOptions,
|
|
15
16
|
DBPatch,
|
|
@@ -301,11 +302,11 @@ export class RunnableDBQuery<
|
|
|
301
302
|
await this.dao.streamQueryAsDBMForEach(this, mapper, opt)
|
|
302
303
|
}
|
|
303
304
|
|
|
304
|
-
streamQuery(opt?: CommonDaoStreamOptions): ReadableTyped<Saved<BM>> {
|
|
305
|
+
streamQuery(opt?: CommonDaoStreamOptions<Saved<BM>>): ReadableTyped<Saved<BM>> {
|
|
305
306
|
return this.dao.streamQuery(this, opt)
|
|
306
307
|
}
|
|
307
308
|
|
|
308
|
-
streamQueryAsDBM(opt?: CommonDaoStreamOptions): ReadableTyped<DBM> {
|
|
309
|
+
streamQueryAsDBM(opt?: CommonDaoStreamOptions<DBM>): ReadableTyped<DBM> {
|
|
309
310
|
return this.dao.streamQueryAsDBM(this, opt)
|
|
310
311
|
}
|
|
311
312
|
|
|
@@ -313,7 +314,7 @@ export class RunnableDBQuery<
|
|
|
313
314
|
return await this.dao.queryIds(this, opt)
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
streamQueryIds(opt?: CommonDaoStreamOptions): ReadableTyped<ID> {
|
|
317
|
+
streamQueryIds(opt?: CommonDaoStreamOptions<ID>): ReadableTyped<ID> {
|
|
317
318
|
return this.dao.streamQueryIds(this, opt)
|
|
318
319
|
}
|
|
319
320
|
|
|
@@ -324,9 +325,7 @@ export class RunnableDBQuery<
|
|
|
324
325
|
await this.dao.streamQueryIdsForEach(this, mapper, opt)
|
|
325
326
|
}
|
|
326
327
|
|
|
327
|
-
async deleteByQuery(
|
|
328
|
-
opt?: CommonDaoStreamForEachOptions<DBM> & { stream?: boolean },
|
|
329
|
-
): Promise<number> {
|
|
328
|
+
async deleteByQuery(opt?: CommonDaoStreamDeleteOptions<DBM>): Promise<number> {
|
|
330
329
|
return await this.dao.deleteByQuery(this, opt)
|
|
331
330
|
}
|
|
332
331
|
}
|
package/src/testing/daoTest.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Readable } from 'node:stream'
|
|
2
|
+
import { pDelay, _deepCopy, _pick, _sortBy, _omit, localTimeNow } from '@naturalcycles/js-lib'
|
|
3
|
+
import { _pipeline, readableToArray, transformNoOp } from '@naturalcycles/nodejs-lib'
|
|
3
4
|
import { CommonDaoLogLevel, DBQuery } from '..'
|
|
4
5
|
import { CommonDB } from '../common.db'
|
|
5
6
|
import { CommonDao } from '../commondao/common.dao'
|
|
@@ -252,6 +253,19 @@ export function runCommonDaoTest(
|
|
|
252
253
|
quirks,
|
|
253
254
|
)
|
|
254
255
|
})
|
|
256
|
+
|
|
257
|
+
test('streamSaveTransform', async () => {
|
|
258
|
+
const items2 = createTestItemsBM(2).map(i => ({ ...i, id: i.id + '_str' }))
|
|
259
|
+
const ids = items2.map(i => i.id)
|
|
260
|
+
|
|
261
|
+
await _pipeline([Readable.from(items2), ...dao.streamSaveTransform()])
|
|
262
|
+
|
|
263
|
+
const items2Loaded = await dao.getByIds(ids)
|
|
264
|
+
expectMatch(items2, items2Loaded, quirks)
|
|
265
|
+
|
|
266
|
+
// cleanup
|
|
267
|
+
await dao.query().filterIn('id', ids).deleteByQuery()
|
|
268
|
+
})
|
|
255
269
|
}
|
|
256
270
|
|
|
257
271
|
// DELETE BY
|
|
@@ -275,7 +289,7 @@ export function runCommonDaoTest(
|
|
|
275
289
|
await dao.query().deleteByQuery()
|
|
276
290
|
|
|
277
291
|
// Test that id, created, updated are created
|
|
278
|
-
const now =
|
|
292
|
+
const now = localTimeNow().unix()
|
|
279
293
|
await dao.runInTransaction([dao.tx.save(_omit(item1, ['id', 'created', 'updated']))])
|
|
280
294
|
|
|
281
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`)])
|