@naturalcycles/db-lib 8.53.0 → 8.54.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/localFile.persistence.plugin.js +6 -5
- package/dist/adapter/inmemory/inMemory.db.js +5 -4
- package/dist/commondao/common.dao.d.ts +4 -4
- package/dist/commondao/common.dao.js +106 -22
- package/dist/commondao/common.dao.model.d.ts +29 -2
- package/dist/pipeline/dbPipelineBackup.js +7 -6
- package/dist/pipeline/dbPipelineRestore.js +3 -3
- package/package.json +3 -4
- package/src/adapter/file/localFile.persistence.plugin.ts +8 -5
- package/src/adapter/inmemory/inMemory.db.ts +7 -4
- package/src/commondao/common.dao.model.ts +31 -1
- package/src/commondao/common.dao.ts +141 -31
- package/src/pipeline/dbPipelineBackup.ts +11 -6
- package/src/pipeline/dbPipelineRestore.ts +6 -3
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LocalFilePersistencePlugin = void 0;
|
|
4
|
+
const fs = require("node:fs");
|
|
4
5
|
const node_stream_1 = require("node:stream");
|
|
6
|
+
const fsp = require("node:fs/promises");
|
|
5
7
|
const node_zlib_1 = require("node:zlib");
|
|
6
8
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
7
9
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
8
|
-
const fs = require("fs-extra");
|
|
9
10
|
/**
|
|
10
11
|
* Persists in local filesystem as ndjson.
|
|
11
12
|
*/
|
|
@@ -19,15 +20,15 @@ class LocalFilePersistencePlugin {
|
|
|
19
20
|
}
|
|
20
21
|
async ping() { }
|
|
21
22
|
async getTables() {
|
|
22
|
-
return (await
|
|
23
|
+
return (await fsp.readdir(this.cfg.storagePath))
|
|
23
24
|
.filter(f => f.includes('.ndjson'))
|
|
24
25
|
.map(f => f.split('.ndjson')[0]);
|
|
25
26
|
}
|
|
26
27
|
async loadFile(table) {
|
|
27
|
-
await
|
|
28
|
+
await (0, nodejs_lib_1._ensureDir)(this.cfg.storagePath);
|
|
28
29
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`;
|
|
29
30
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`;
|
|
30
|
-
if (!(await
|
|
31
|
+
if (!(await (0, nodejs_lib_1._pathExists)(filePath)))
|
|
31
32
|
return [];
|
|
32
33
|
const transformUnzip = this.cfg.gzip ? [(0, node_zlib_1.createUnzip)()] : [];
|
|
33
34
|
const rows = [];
|
|
@@ -44,7 +45,7 @@ class LocalFilePersistencePlugin {
|
|
|
44
45
|
await (0, js_lib_1.pMap)(ops, async (op) => await this.saveFile(op.table, op.rows), { concurrency: 16 });
|
|
45
46
|
}
|
|
46
47
|
async saveFile(table, rows) {
|
|
47
|
-
await
|
|
48
|
+
await (0, nodejs_lib_1._ensureDir)(this.cfg.storagePath);
|
|
48
49
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`;
|
|
49
50
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`;
|
|
50
51
|
const transformZip = this.cfg.gzip ? [(0, node_zlib_1.createGzip)()] : [];
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.InMemoryDB = void 0;
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const fsp = require("node:fs/promises");
|
|
4
6
|
const node_stream_1 = require("node:stream");
|
|
5
7
|
const node_zlib_1 = require("node:zlib");
|
|
6
8
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
7
9
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
8
10
|
const colors_1 = require("@naturalcycles/nodejs-lib/dist/colors");
|
|
9
|
-
const fs = require("fs-extra");
|
|
10
11
|
const __1 = require("../..");
|
|
11
12
|
const dbQuery_1 = require("../../query/dbQuery");
|
|
12
13
|
class InMemoryDB {
|
|
@@ -166,7 +167,7 @@ class InMemoryDB {
|
|
|
166
167
|
(0, js_lib_1._assert)(this.cfg.persistenceEnabled, 'flushToDisk() called but persistenceEnabled=false');
|
|
167
168
|
const { persistentStoragePath, persistZip } = this.cfg;
|
|
168
169
|
const started = Date.now();
|
|
169
|
-
await
|
|
170
|
+
await (0, nodejs_lib_1._emptyDir)(persistentStoragePath);
|
|
170
171
|
const transformZip = persistZip ? [(0, node_zlib_1.createGzip)()] : [];
|
|
171
172
|
let tables = 0;
|
|
172
173
|
// infinite concurrency for now
|
|
@@ -192,9 +193,9 @@ class InMemoryDB {
|
|
|
192
193
|
(0, js_lib_1._assert)(this.cfg.persistenceEnabled, 'restoreFromDisk() called but persistenceEnabled=false');
|
|
193
194
|
const { persistentStoragePath } = this.cfg;
|
|
194
195
|
const started = Date.now();
|
|
195
|
-
await
|
|
196
|
+
await (0, nodejs_lib_1._ensureDir)(persistentStoragePath);
|
|
196
197
|
this.data = {}; // empty it in the beginning!
|
|
197
|
-
const files = (await
|
|
198
|
+
const files = (await fsp.readdir(persistentStoragePath)).filter(f => f.includes('.ndjson'));
|
|
198
199
|
// infinite concurrency for now
|
|
199
200
|
await (0, js_lib_1.pMap)(files, async (file) => {
|
|
200
201
|
const fname = `${persistentStoragePath}/${file}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyObject, AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Promisable, Saved, Unsaved, ZodSchema } from '@naturalcycles/js-lib';
|
|
1
|
+
import { AnyObject, AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Promisable, Saved, UnixTimestampMillisNumber, Unsaved, ZodSchema } from '@naturalcycles/js-lib';
|
|
2
2
|
import { AjvSchema, ObjectSchemaTyped, ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
3
|
import { DBDeleteByIdsOperation, DBModelType, DBOperation, DBPatch, DBSaveBatchOperation, RunQueryResult } from '../db.model';
|
|
4
4
|
import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
|
|
@@ -84,7 +84,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
84
84
|
assignIdCreatedUpdated(obj: BM, opt?: CommonDaoOptions): Saved<BM>;
|
|
85
85
|
assignIdCreatedUpdated(obj: Unsaved<BM>, opt?: CommonDaoOptions): Saved<BM>;
|
|
86
86
|
tx: {
|
|
87
|
-
save: (bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<DBM>) => Promise<DBSaveBatchOperation>;
|
|
87
|
+
save: (bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<DBM>) => Promise<DBSaveBatchOperation | undefined>;
|
|
88
88
|
saveBatch: (bms: Unsaved<BM>[], opt?: CommonDaoSaveOptions<DBM>) => Promise<DBSaveBatchOperation | undefined>;
|
|
89
89
|
deleteByIds: (ids: ID[], opt?: CommonDaoOptions) => Promise<DBDeleteByIdsOperation | undefined>;
|
|
90
90
|
deleteById: (id: ID | null | undefined, opt?: CommonDaoOptions) => Promise<DBDeleteByIdsOperation | undefined>;
|
|
@@ -155,6 +155,6 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
155
155
|
runInTransaction(ops: Promisable<DBOperation | undefined>[]): Promise<void>;
|
|
156
156
|
protected logResult(started: number, op: string, res: any, table: string): void;
|
|
157
157
|
protected logSaveResult(started: number, op: string, table: string): void;
|
|
158
|
-
protected logStarted(op: string, table: string, force?: boolean):
|
|
159
|
-
protected logSaveStarted(op: string, items: any, table: string):
|
|
158
|
+
protected logStarted(op: string, table: string, force?: boolean): UnixTimestampMillisNumber;
|
|
159
|
+
protected logSaveStarted(op: string, items: any, table: string): UnixTimestampMillisNumber;
|
|
160
160
|
}
|
|
@@ -22,7 +22,10 @@ class CommonDao {
|
|
|
22
22
|
this.cfg = cfg;
|
|
23
23
|
this.tx = {
|
|
24
24
|
save: async (bm, opt = {}) => {
|
|
25
|
+
// .save actually returns DBM (not BM) when it detects `opt.tx === true`
|
|
25
26
|
const row = (await this.save(bm, { ...opt, tx: true }));
|
|
27
|
+
if (row === null)
|
|
28
|
+
return;
|
|
26
29
|
return {
|
|
27
30
|
type: 'saveBatch',
|
|
28
31
|
table: this.cfg.table,
|
|
@@ -34,9 +37,9 @@ class CommonDao {
|
|
|
34
37
|
};
|
|
35
38
|
},
|
|
36
39
|
saveBatch: async (bms, opt = {}) => {
|
|
37
|
-
if (!bms.length)
|
|
38
|
-
return;
|
|
39
40
|
const rows = (await this.saveBatch(bms, { ...opt, tx: true }));
|
|
41
|
+
if (!rows.length)
|
|
42
|
+
return;
|
|
40
43
|
return {
|
|
41
44
|
type: 'saveBatch',
|
|
42
45
|
table: this.cfg.table,
|
|
@@ -113,7 +116,10 @@ class CommonDao {
|
|
|
113
116
|
const op = `getById(${id})`;
|
|
114
117
|
const table = opt.table || this.cfg.table;
|
|
115
118
|
const started = this.logStarted(op, table);
|
|
116
|
-
|
|
119
|
+
let dbm = (await this.cfg.db.getByIds(table, [id]))[0];
|
|
120
|
+
if (dbm && !opt.raw && this.cfg.hooks.afterLoad) {
|
|
121
|
+
dbm = (await this.cfg.hooks.afterLoad(dbm)) || undefined;
|
|
122
|
+
}
|
|
117
123
|
const bm = opt.raw ? dbm : await this.dbmToBM(dbm, opt);
|
|
118
124
|
this.logResult(started, op, bm, table);
|
|
119
125
|
return bm || null;
|
|
@@ -138,6 +144,9 @@ class CommonDao {
|
|
|
138
144
|
const table = opt.table || this.cfg.table;
|
|
139
145
|
const started = this.logStarted(op, table);
|
|
140
146
|
let [dbm] = await this.cfg.db.getByIds(table, [id]);
|
|
147
|
+
if (dbm && !opt.raw && this.cfg.hooks.afterLoad) {
|
|
148
|
+
dbm = (await this.cfg.hooks.afterLoad(dbm)) || undefined;
|
|
149
|
+
}
|
|
141
150
|
if (!opt.raw) {
|
|
142
151
|
dbm = this.anyToDBM(dbm, opt);
|
|
143
152
|
}
|
|
@@ -150,7 +159,10 @@ class CommonDao {
|
|
|
150
159
|
const op = `getByIdAsTM(${id})`;
|
|
151
160
|
const table = opt.table || this.cfg.table;
|
|
152
161
|
const started = this.logStarted(op, table);
|
|
153
|
-
|
|
162
|
+
let [dbm] = await this.cfg.db.getByIds(table, [id]);
|
|
163
|
+
if (dbm && !opt.raw && this.cfg.hooks.afterLoad) {
|
|
164
|
+
dbm = (await this.cfg.hooks.afterLoad(dbm)) || undefined;
|
|
165
|
+
}
|
|
154
166
|
if (opt.raw) {
|
|
155
167
|
this.logResult(started, op, dbm, table);
|
|
156
168
|
return dbm || null;
|
|
@@ -161,19 +173,29 @@ class CommonDao {
|
|
|
161
173
|
return tm || null;
|
|
162
174
|
}
|
|
163
175
|
async getByIds(ids, opt = {}) {
|
|
176
|
+
if (!ids.length)
|
|
177
|
+
return [];
|
|
164
178
|
const op = `getByIds ${ids.length} id(s) (${(0, js_lib_1._truncate)(ids.slice(0, 10).join(', '), 50)})`;
|
|
165
179
|
const table = opt.table || this.cfg.table;
|
|
166
180
|
const started = this.logStarted(op, table);
|
|
167
|
-
|
|
181
|
+
let dbms = await this.cfg.db.getByIds(table, ids);
|
|
182
|
+
if (!opt.raw && this.cfg.hooks.afterLoad && dbms.length) {
|
|
183
|
+
dbms = (await (0, js_lib_1.pMap)(dbms, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
|
|
184
|
+
}
|
|
168
185
|
const bms = opt.raw ? dbms : await this.dbmsToBM(dbms, opt);
|
|
169
186
|
this.logResult(started, op, bms, table);
|
|
170
187
|
return bms;
|
|
171
188
|
}
|
|
172
189
|
async getByIdsAsDBM(ids, opt = {}) {
|
|
190
|
+
if (!ids.length)
|
|
191
|
+
return [];
|
|
173
192
|
const op = `getByIdsAsDBM ${ids.length} id(s) (${(0, js_lib_1._truncate)(ids.slice(0, 10).join(', '), 50)})`;
|
|
174
193
|
const table = opt.table || this.cfg.table;
|
|
175
194
|
const started = this.logStarted(op, table);
|
|
176
|
-
|
|
195
|
+
let dbms = await this.cfg.db.getByIds(table, ids);
|
|
196
|
+
if (!opt.raw && this.cfg.hooks.afterLoad && dbms.length) {
|
|
197
|
+
dbms = (await (0, js_lib_1.pMap)(dbms, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
|
|
198
|
+
}
|
|
177
199
|
this.logResult(started, op, dbms, table);
|
|
178
200
|
return dbms;
|
|
179
201
|
}
|
|
@@ -272,8 +294,11 @@ class CommonDao {
|
|
|
272
294
|
q.table = opt.table || q.table;
|
|
273
295
|
const op = `runQuery(${q.pretty()})`;
|
|
274
296
|
const started = this.logStarted(op, q.table);
|
|
275
|
-
|
|
297
|
+
let { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
|
|
276
298
|
const partialQuery = !!q._selectedFieldNames;
|
|
299
|
+
if (!opt.raw && this.cfg.hooks.afterLoad && rows.length) {
|
|
300
|
+
rows = (await (0, js_lib_1.pMap)(rows, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
|
|
301
|
+
}
|
|
277
302
|
const bms = partialQuery || opt.raw ? rows : await this.dbmsToBM(rows, opt);
|
|
278
303
|
this.logResult(started, op, bms, q.table);
|
|
279
304
|
return {
|
|
@@ -289,7 +314,10 @@ class CommonDao {
|
|
|
289
314
|
q.table = opt.table || q.table;
|
|
290
315
|
const op = `runQueryAsDBM(${q.pretty()})`;
|
|
291
316
|
const started = this.logStarted(op, q.table);
|
|
292
|
-
|
|
317
|
+
let { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
|
|
318
|
+
if (!opt.raw && this.cfg.hooks.afterLoad && rows.length) {
|
|
319
|
+
rows = (await (0, js_lib_1.pMap)(rows, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
|
|
320
|
+
}
|
|
293
321
|
const partialQuery = !!q._selectedFieldNames;
|
|
294
322
|
const dbms = partialQuery || opt.raw ? rows : this.anyToDBMs(rows, opt);
|
|
295
323
|
this.logResult(started, op, dbms, q.table);
|
|
@@ -303,7 +331,10 @@ class CommonDao {
|
|
|
303
331
|
q.table = opt.table || q.table;
|
|
304
332
|
const op = `runQueryAsTM(${q.pretty()})`;
|
|
305
333
|
const started = this.logStarted(op, q.table);
|
|
306
|
-
|
|
334
|
+
let { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
|
|
335
|
+
if (!opt.raw && this.cfg.hooks.afterLoad && rows.length) {
|
|
336
|
+
rows = (await (0, js_lib_1.pMap)(rows, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
|
|
337
|
+
}
|
|
307
338
|
const partialQuery = !!q._selectedFieldNames;
|
|
308
339
|
const tms = partialQuery || opt.raw ? rows : this.bmsToTM(await this.dbmsToBM(rows, opt), opt);
|
|
309
340
|
this.logResult(started, op, tms, q.table);
|
|
@@ -333,11 +364,16 @@ class CommonDao {
|
|
|
333
364
|
let count = 0;
|
|
334
365
|
await (0, nodejs_lib_1._pipeline)([
|
|
335
366
|
this.cfg.db.streamQuery(q, opt),
|
|
336
|
-
// optimization: 1 validation is enough
|
|
337
|
-
// transformMap<any, DBM>(dbm => (partialQuery || opt.raw ? dbm : this.anyToDBM(dbm, opt)), opt),
|
|
338
367
|
(0, nodejs_lib_1.transformMap)(async (dbm) => {
|
|
339
368
|
count++;
|
|
340
|
-
|
|
369
|
+
if (partialQuery || opt.raw)
|
|
370
|
+
return dbm;
|
|
371
|
+
if (this.cfg.hooks.afterLoad) {
|
|
372
|
+
dbm = (await this.cfg.hooks.afterLoad(dbm));
|
|
373
|
+
if (dbm === null)
|
|
374
|
+
return js_lib_1.SKIP;
|
|
375
|
+
}
|
|
376
|
+
return await this.dbmToBM(dbm, opt);
|
|
341
377
|
}, {
|
|
342
378
|
errorMode: opt.errorMode,
|
|
343
379
|
}),
|
|
@@ -367,9 +403,16 @@ class CommonDao {
|
|
|
367
403
|
let count = 0;
|
|
368
404
|
await (0, nodejs_lib_1._pipeline)([
|
|
369
405
|
this.cfg.db.streamQuery(q, opt),
|
|
370
|
-
(0, nodejs_lib_1.
|
|
406
|
+
(0, nodejs_lib_1.transformMap)(async (dbm) => {
|
|
371
407
|
count++;
|
|
372
|
-
|
|
408
|
+
if (partialQuery || opt.raw)
|
|
409
|
+
return dbm;
|
|
410
|
+
if (this.cfg.hooks.afterLoad) {
|
|
411
|
+
dbm = (await this.cfg.hooks.afterLoad(dbm));
|
|
412
|
+
if (dbm === null)
|
|
413
|
+
return js_lib_1.SKIP;
|
|
414
|
+
}
|
|
415
|
+
return this.anyToDBM(dbm, opt);
|
|
373
416
|
}, {
|
|
374
417
|
errorMode: opt.errorMode,
|
|
375
418
|
}),
|
|
@@ -402,8 +445,15 @@ class CommonDao {
|
|
|
402
445
|
return stream;
|
|
403
446
|
return stream
|
|
404
447
|
.on('error', err => stream.emit('error', err))
|
|
405
|
-
.pipe((0, nodejs_lib_1.
|
|
406
|
-
|
|
448
|
+
.pipe((0, nodejs_lib_1.transformMap)(async (dbm) => {
|
|
449
|
+
if (this.cfg.hooks.afterLoad) {
|
|
450
|
+
dbm = (await this.cfg.hooks.afterLoad(dbm));
|
|
451
|
+
if (dbm === null)
|
|
452
|
+
return js_lib_1.SKIP;
|
|
453
|
+
}
|
|
454
|
+
return this.anyToDBM(dbm, opt);
|
|
455
|
+
}, {
|
|
456
|
+
errorMode: opt.errorMode,
|
|
407
457
|
}));
|
|
408
458
|
}
|
|
409
459
|
/**
|
|
@@ -429,8 +479,15 @@ class CommonDao {
|
|
|
429
479
|
// .pipe(transformMap<any, DBM>(dbm => this.anyToDBM(dbm, opt), safeOpt))
|
|
430
480
|
// .pipe(transformMap<DBM, Saved<BM>>(dbm => this.dbmToBM(dbm, opt), safeOpt))
|
|
431
481
|
.on('error', err => stream.emit('error', err))
|
|
432
|
-
.pipe((0, nodejs_lib_1.transformMap)(async (dbm) =>
|
|
433
|
-
|
|
482
|
+
.pipe((0, nodejs_lib_1.transformMap)(async (dbm) => {
|
|
483
|
+
if (this.cfg.hooks.afterLoad) {
|
|
484
|
+
dbm = (await this.cfg.hooks.afterLoad(dbm));
|
|
485
|
+
if (dbm === null)
|
|
486
|
+
return js_lib_1.SKIP;
|
|
487
|
+
}
|
|
488
|
+
return await this.dbmToBM(dbm, opt);
|
|
489
|
+
}, {
|
|
490
|
+
errorMode: opt.errorMode,
|
|
434
491
|
}))
|
|
435
492
|
// this can make the stream async-iteration-friendly
|
|
436
493
|
// but not applying it now for perf reasons
|
|
@@ -461,8 +518,10 @@ class CommonDao {
|
|
|
461
518
|
let count = 0;
|
|
462
519
|
await (0, nodejs_lib_1._pipeline)([
|
|
463
520
|
this.cfg.db.streamQuery(q.select(['id']), opt),
|
|
464
|
-
(0, nodejs_lib_1.transformMapSimple)(objectWithId =>
|
|
465
|
-
|
|
521
|
+
(0, nodejs_lib_1.transformMapSimple)(objectWithId => {
|
|
522
|
+
count++;
|
|
523
|
+
return objectWithId.id;
|
|
524
|
+
}),
|
|
466
525
|
(0, nodejs_lib_1.transformMap)(mapper, {
|
|
467
526
|
...opt,
|
|
468
527
|
predicate: js_lib_1._passthroughPredicate,
|
|
@@ -502,8 +561,14 @@ class CommonDao {
|
|
|
502
561
|
this.requireWriteAccess();
|
|
503
562
|
const idWasGenerated = !bm.id && this.cfg.createId;
|
|
504
563
|
this.assignIdCreatedUpdated(bm, opt); // mutates
|
|
505
|
-
|
|
564
|
+
let dbm = await this.bmToDBM(bm, opt);
|
|
565
|
+
if (this.cfg.hooks.beforeSave) {
|
|
566
|
+
dbm = (await this.cfg.hooks.beforeSave(dbm));
|
|
567
|
+
if (dbm === null && !opt.tx)
|
|
568
|
+
return bm;
|
|
569
|
+
}
|
|
506
570
|
if (opt.tx) {
|
|
571
|
+
// May return `null`, in which case it'll be skipped
|
|
507
572
|
return dbm;
|
|
508
573
|
}
|
|
509
574
|
const table = opt.table || this.cfg.table;
|
|
@@ -569,6 +634,11 @@ class CommonDao {
|
|
|
569
634
|
const started = this.logSaveStarted(op, row, table);
|
|
570
635
|
const { excludeFromIndexes } = this.cfg;
|
|
571
636
|
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
|
|
637
|
+
if (this.cfg.hooks.beforeSave) {
|
|
638
|
+
row = (await this.cfg.hooks.beforeSave(row));
|
|
639
|
+
if (row === null)
|
|
640
|
+
return dbm;
|
|
641
|
+
}
|
|
572
642
|
await this.cfg.db.saveBatch(table, [row], {
|
|
573
643
|
excludeFromIndexes,
|
|
574
644
|
assignGeneratedIds,
|
|
@@ -581,10 +651,15 @@ class CommonDao {
|
|
|
581
651
|
return row;
|
|
582
652
|
}
|
|
583
653
|
async saveBatch(bms, opt = {}) {
|
|
654
|
+
if (!bms.length)
|
|
655
|
+
return [];
|
|
584
656
|
this.requireWriteAccess();
|
|
585
657
|
const table = opt.table || this.cfg.table;
|
|
586
658
|
bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt));
|
|
587
|
-
|
|
659
|
+
let dbms = await this.bmsToDBM(bms, opt);
|
|
660
|
+
if (this.cfg.hooks.beforeSave && dbms.length) {
|
|
661
|
+
dbms = (await (0, js_lib_1.pMap)(dbms, async (dbm) => await this.cfg.hooks.beforeSave(dbm))).filter(js_lib_1._isTruthy);
|
|
662
|
+
}
|
|
588
663
|
if (opt.tx) {
|
|
589
664
|
return dbms;
|
|
590
665
|
}
|
|
@@ -612,6 +687,8 @@ class CommonDao {
|
|
|
612
687
|
return bms;
|
|
613
688
|
}
|
|
614
689
|
async saveBatchAsDBM(dbms, opt = {}) {
|
|
690
|
+
if (!dbms.length)
|
|
691
|
+
return [];
|
|
615
692
|
this.requireWriteAccess();
|
|
616
693
|
const table = opt.table || this.cfg.table;
|
|
617
694
|
let rows = dbms;
|
|
@@ -631,6 +708,9 @@ class CommonDao {
|
|
|
631
708
|
const started = this.logSaveStarted(op, rows, table);
|
|
632
709
|
const { excludeFromIndexes } = this.cfg;
|
|
633
710
|
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
|
|
711
|
+
if (this.cfg.hooks.beforeSave && rows.length) {
|
|
712
|
+
rows = (await (0, js_lib_1.pMap)(rows, async (row) => await this.cfg.hooks.beforeSave(row))).filter(js_lib_1._isTruthy);
|
|
713
|
+
}
|
|
634
714
|
await this.cfg.db.saveBatch(table, rows, {
|
|
635
715
|
excludeFromIndexes,
|
|
636
716
|
assignGeneratedIds,
|
|
@@ -655,6 +735,8 @@ class CommonDao {
|
|
|
655
735
|
return count;
|
|
656
736
|
}
|
|
657
737
|
async deleteByIds(ids, opt = {}) {
|
|
738
|
+
if (!ids.length)
|
|
739
|
+
return 0;
|
|
658
740
|
this.requireWriteAccess();
|
|
659
741
|
this.requireObjectMutability(opt);
|
|
660
742
|
const op = `deleteByIds(${ids.join(', ')})`;
|
|
@@ -709,6 +791,8 @@ class CommonDao {
|
|
|
709
791
|
return await this.updateByQuery(this.query().filterEq('id', id), patch, opt);
|
|
710
792
|
}
|
|
711
793
|
async updateByIds(ids, patch, opt = {}) {
|
|
794
|
+
if (!ids.length)
|
|
795
|
+
return 0;
|
|
712
796
|
return await this.updateByQuery(this.query().filterIn('id', ids), patch, opt);
|
|
713
797
|
}
|
|
714
798
|
async updateByQuery(q, patch, opt = {}) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CommonLogger, ErrorMode, ObjectWithId, Saved, ZodError, ZodSchema } from '@naturalcycles/js-lib';
|
|
1
|
+
import { CommonLogger, ErrorMode, ObjectWithId, Promisable, Saved, ZodError, ZodSchema } from '@naturalcycles/js-lib';
|
|
2
2
|
import { AjvSchema, AjvValidationError, JoiValidationError, ObjectSchemaTyped, TransformLogProgressOptions, TransformMapOptions } from '@naturalcycles/nodejs-lib';
|
|
3
3
|
import { CommonDB } from '../common.db';
|
|
4
4
|
import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model';
|
|
@@ -30,7 +30,7 @@ export interface CommonDaoHooks<BM extends Partial<ObjectWithId<ID>>, DBM extend
|
|
|
30
30
|
beforeCreate: (bm: Partial<BM>) => Partial<BM>;
|
|
31
31
|
/**
|
|
32
32
|
* Called when loading things "as DBM" and validation is not skipped.
|
|
33
|
-
* When loading things
|
|
33
|
+
* When loading things as BM/TM - other hooks get involved instead:
|
|
34
34
|
* - beforeDBMToBM
|
|
35
35
|
* - beforeBMToTM
|
|
36
36
|
*
|
|
@@ -41,6 +41,33 @@ export interface CommonDaoHooks<BM extends Partial<ObjectWithId<ID>>, DBM extend
|
|
|
41
41
|
beforeDBMToBM: (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>;
|
|
42
42
|
beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>;
|
|
43
43
|
beforeBMToTM: (bm: BM) => Partial<TM>;
|
|
44
|
+
/**
|
|
45
|
+
* Allows to access the DBM just after it has been loaded from the DB.
|
|
46
|
+
*
|
|
47
|
+
* Normally does nothing.
|
|
48
|
+
*
|
|
49
|
+
* You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
|
|
50
|
+
* to pass it further.
|
|
51
|
+
*
|
|
52
|
+
* You can return `null` to make it look "not found".
|
|
53
|
+
*
|
|
54
|
+
* You can do validations as needed here and throw errors, they will be propagated.
|
|
55
|
+
*/
|
|
56
|
+
afterLoad?: (dbm: DBM) => Promisable<DBM | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Allows to access the DBM just before it's supposed to be saved to the DB.
|
|
59
|
+
*
|
|
60
|
+
* Normally does nothing.
|
|
61
|
+
*
|
|
62
|
+
* You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
|
|
63
|
+
* to pass it further.
|
|
64
|
+
*
|
|
65
|
+
* You can return `null` to prevent it from being saved, without throwing an error.
|
|
66
|
+
* `.save` method will then return the BM/DBM as it has entered the method (it **won't** return the null value!).
|
|
67
|
+
*
|
|
68
|
+
* You can do validations as needed here and throw errors, they will be propagated.
|
|
69
|
+
*/
|
|
70
|
+
beforeSave?: (dbm: DBM) => Promisable<DBM | null>;
|
|
44
71
|
/**
|
|
45
72
|
* Called in:
|
|
46
73
|
* - dbmToBM (applied before DBM becomes BM)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dbPipelineBackup = void 0;
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const fsp = require("node:fs/promises");
|
|
4
6
|
const node_zlib_1 = require("node:zlib");
|
|
5
7
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
8
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
7
9
|
const colors_1 = require("@naturalcycles/nodejs-lib/dist/colors");
|
|
8
|
-
const fs = require("fs-extra");
|
|
9
10
|
const index_1 = require("../index");
|
|
10
11
|
/**
|
|
11
12
|
* Pipeline from input stream(s) to a NDJSON file (optionally gzipped).
|
|
@@ -23,7 +24,7 @@ async function dbPipelineBackup(opt) {
|
|
|
23
24
|
let { tables } = opt;
|
|
24
25
|
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0, colors_1.grey)((0, js_lib_1.localTime)(sinceUpdated).toPretty()) : '';
|
|
25
26
|
console.log(`>> ${(0, colors_1.dimWhite)('dbPipelineBackup')} started in ${(0, colors_1.grey)(outputDirPath)}...${sinceUpdatedStr}`);
|
|
26
|
-
|
|
27
|
+
(0, nodejs_lib_1._ensureDirSync)(outputDirPath);
|
|
27
28
|
tables ||= await db.getTables();
|
|
28
29
|
console.log(`${(0, colors_1.yellow)(tables.length)} ${(0, colors_1.boldWhite)('table(s)')}:\n` + tables.join('\n'));
|
|
29
30
|
const statsPerTable = {};
|
|
@@ -34,16 +35,16 @@ async function dbPipelineBackup(opt) {
|
|
|
34
35
|
}
|
|
35
36
|
const filePath = `${outputDirPath}/${table}.ndjson` + (gzip ? '.gz' : '');
|
|
36
37
|
const schemaFilePath = `${outputDirPath}/${table}.schema.json`;
|
|
37
|
-
if (protectFromOverwrite && (
|
|
38
|
+
if (protectFromOverwrite && (0, nodejs_lib_1._pathExistsSync)(filePath)) {
|
|
38
39
|
throw new js_lib_1.AppError(`dbPipelineBackup: output file exists: ${filePath}`);
|
|
39
40
|
}
|
|
40
41
|
const started = Date.now();
|
|
41
42
|
let rows = 0;
|
|
42
|
-
|
|
43
|
+
(0, nodejs_lib_1._ensureFileSync)(filePath);
|
|
43
44
|
console.log(`>> ${(0, colors_1.grey)(filePath)} started...`);
|
|
44
45
|
if (emitSchemaFromDB) {
|
|
45
46
|
const schema = await db.getTableSchema(table);
|
|
46
|
-
await
|
|
47
|
+
await (0, nodejs_lib_1._writeJsonFile)(schemaFilePath, schema, { spaces: 2 });
|
|
47
48
|
console.log(`>> ${(0, colors_1.grey)(schemaFilePath)} saved (generated from DB)`);
|
|
48
49
|
}
|
|
49
50
|
await (0, nodejs_lib_1._pipeline)([
|
|
@@ -66,7 +67,7 @@ async function dbPipelineBackup(opt) {
|
|
|
66
67
|
...(gzip ? [(0, node_zlib_1.createGzip)(zlibOptions)] : []),
|
|
67
68
|
fs.createWriteStream(filePath),
|
|
68
69
|
]);
|
|
69
|
-
const { size: sizeBytes } = await
|
|
70
|
+
const { size: sizeBytes } = await fsp.stat(filePath);
|
|
70
71
|
const stats = nodejs_lib_1.NDJsonStats.create({
|
|
71
72
|
tookMillis: Date.now() - started,
|
|
72
73
|
rows,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dbPipelineRestore = void 0;
|
|
4
|
+
const fs = require("node:fs");
|
|
4
5
|
const node_zlib_1 = require("node:zlib");
|
|
5
6
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
7
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
7
8
|
const colors_1 = require("@naturalcycles/nodejs-lib/dist/colors");
|
|
8
|
-
const fs = require("fs-extra");
|
|
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.
|
|
@@ -19,7 +19,7 @@ async function dbPipelineRestore(opt) {
|
|
|
19
19
|
const onlyTables = opt.tables && new Set(opt.tables);
|
|
20
20
|
const sinceUpdatedStr = sinceUpdated ? ' since ' + (0, colors_1.grey)((0, js_lib_1.localTime)(sinceUpdated).toPretty()) : '';
|
|
21
21
|
console.log(`>> ${(0, colors_1.dimWhite)('dbPipelineRestore')} started in ${(0, colors_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 = {};
|
|
@@ -54,7 +54,7 @@ async function dbPipelineRestore(opt) {
|
|
|
54
54
|
console.warn(`${schemaFilePath} does not exist!`);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
const schema = await
|
|
57
|
+
const schema = await (0, nodejs_lib_1._readJsonFile)(schemaFilePath);
|
|
58
58
|
await db.createTable(table, schema, { dropIfExists: true });
|
|
59
59
|
});
|
|
60
60
|
}
|
package/package.json
CHANGED
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@naturalcycles/js-lib": "^14.116.0",
|
|
8
|
-
"@naturalcycles/nodejs-lib": "^12.0.0"
|
|
9
|
-
"fs-extra": "^11.1.0"
|
|
8
|
+
"@naturalcycles/nodejs-lib": "^12.0.0"
|
|
10
9
|
},
|
|
11
10
|
"devDependencies": {
|
|
12
11
|
"@naturalcycles/bench-lib": "^1.0.0",
|
|
13
12
|
"@naturalcycles/dev-lib": "^13.0.0",
|
|
14
|
-
"@types/node": "^
|
|
13
|
+
"@types/node": "^20.2.1",
|
|
15
14
|
"jest": "^29.0.0"
|
|
16
15
|
},
|
|
17
16
|
"files": [
|
|
@@ -41,7 +40,7 @@
|
|
|
41
40
|
"engines": {
|
|
42
41
|
"node": ">=18.12"
|
|
43
42
|
},
|
|
44
|
-
"version": "8.
|
|
43
|
+
"version": "8.54.1",
|
|
45
44
|
"description": "Lowest Common Denominator API to supported Databases",
|
|
46
45
|
"keywords": [
|
|
47
46
|
"db",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
1
2
|
import { Readable } from 'node:stream'
|
|
3
|
+
import * as fsp from 'node:fs/promises'
|
|
2
4
|
import { createGzip, createUnzip } from 'node:zlib'
|
|
3
5
|
import { pMap, ObjectWithId } from '@naturalcycles/js-lib'
|
|
4
6
|
import {
|
|
@@ -7,8 +9,9 @@ import {
|
|
|
7
9
|
transformToNDJson,
|
|
8
10
|
writablePushToArray,
|
|
9
11
|
_pipeline,
|
|
12
|
+
_ensureDir,
|
|
13
|
+
_pathExists,
|
|
10
14
|
} from '@naturalcycles/nodejs-lib'
|
|
11
|
-
import * as fs from 'fs-extra'
|
|
12
15
|
import { DBSaveBatchOperation } from '../../db.model'
|
|
13
16
|
import { FileDBPersistencePlugin } from './file.db.model'
|
|
14
17
|
|
|
@@ -41,17 +44,17 @@ export class LocalFilePersistencePlugin implements FileDBPersistencePlugin {
|
|
|
41
44
|
async ping(): Promise<void> {}
|
|
42
45
|
|
|
43
46
|
async getTables(): Promise<string[]> {
|
|
44
|
-
return (await
|
|
47
|
+
return (await fsp.readdir(this.cfg.storagePath))
|
|
45
48
|
.filter(f => f.includes('.ndjson'))
|
|
46
49
|
.map(f => f.split('.ndjson')[0]!)
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
async loadFile<ROW extends ObjectWithId>(table: string): Promise<ROW[]> {
|
|
50
|
-
await
|
|
53
|
+
await _ensureDir(this.cfg.storagePath)
|
|
51
54
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`
|
|
52
55
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`
|
|
53
56
|
|
|
54
|
-
if (!(await
|
|
57
|
+
if (!(await _pathExists(filePath))) return []
|
|
55
58
|
|
|
56
59
|
const transformUnzip = this.cfg.gzip ? [createUnzip()] : []
|
|
57
60
|
|
|
@@ -73,7 +76,7 @@ export class LocalFilePersistencePlugin implements FileDBPersistencePlugin {
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
async saveFile<ROW extends ObjectWithId>(table: string, rows: ROW[]): Promise<void> {
|
|
76
|
-
await
|
|
79
|
+
await _ensureDir(this.cfg.storagePath)
|
|
77
80
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`
|
|
78
81
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`
|
|
79
82
|
const transformZip = this.cfg.gzip ? [createGzip()] : []
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as fsp from 'node:fs/promises'
|
|
1
3
|
import { Readable } from 'node:stream'
|
|
2
4
|
import { createGzip, createUnzip } from 'node:zlib'
|
|
3
5
|
import {
|
|
@@ -23,9 +25,10 @@ import {
|
|
|
23
25
|
transformToNDJson,
|
|
24
26
|
writablePushToArray,
|
|
25
27
|
_pipeline,
|
|
28
|
+
_emptyDir,
|
|
29
|
+
_ensureDir,
|
|
26
30
|
} from '@naturalcycles/nodejs-lib'
|
|
27
31
|
import { dimGrey, yellow } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
28
|
-
import * as fs from 'fs-extra'
|
|
29
32
|
import { CommonDB, DBIncrement, DBPatch, DBTransaction, queryInMemory } from '../..'
|
|
30
33
|
import {
|
|
31
34
|
CommonDBCreateOptions,
|
|
@@ -279,7 +282,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
279
282
|
|
|
280
283
|
const started = Date.now()
|
|
281
284
|
|
|
282
|
-
await
|
|
285
|
+
await _emptyDir(persistentStoragePath)
|
|
283
286
|
|
|
284
287
|
const transformZip = persistZip ? [createGzip()] : []
|
|
285
288
|
let tables = 0
|
|
@@ -314,11 +317,11 @@ export class InMemoryDB implements CommonDB {
|
|
|
314
317
|
|
|
315
318
|
const started = Date.now()
|
|
316
319
|
|
|
317
|
-
await
|
|
320
|
+
await _ensureDir(persistentStoragePath)
|
|
318
321
|
|
|
319
322
|
this.data = {} // empty it in the beginning!
|
|
320
323
|
|
|
321
|
-
const files = (await
|
|
324
|
+
const files = (await fsp.readdir(persistentStoragePath)).filter(f => f.includes('.ndjson'))
|
|
322
325
|
|
|
323
326
|
// infinite concurrency for now
|
|
324
327
|
await pMap(files, async file => {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
CommonLogger,
|
|
3
3
|
ErrorMode,
|
|
4
4
|
ObjectWithId,
|
|
5
|
+
Promisable,
|
|
5
6
|
Saved,
|
|
6
7
|
ZodError,
|
|
7
8
|
ZodSchema,
|
|
@@ -54,7 +55,7 @@ export interface CommonDaoHooks<
|
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
57
|
* Called when loading things "as DBM" and validation is not skipped.
|
|
57
|
-
* When loading things
|
|
58
|
+
* When loading things as BM/TM - other hooks get involved instead:
|
|
58
59
|
* - beforeDBMToBM
|
|
59
60
|
* - beforeBMToTM
|
|
60
61
|
*
|
|
@@ -67,6 +68,35 @@ export interface CommonDaoHooks<
|
|
|
67
68
|
beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>
|
|
68
69
|
beforeBMToTM: (bm: BM) => Partial<TM>
|
|
69
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Allows to access the DBM just after it has been loaded from the DB.
|
|
73
|
+
*
|
|
74
|
+
* Normally does nothing.
|
|
75
|
+
*
|
|
76
|
+
* You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
|
|
77
|
+
* to pass it further.
|
|
78
|
+
*
|
|
79
|
+
* You can return `null` to make it look "not found".
|
|
80
|
+
*
|
|
81
|
+
* You can do validations as needed here and throw errors, they will be propagated.
|
|
82
|
+
*/
|
|
83
|
+
afterLoad?: (dbm: DBM) => Promisable<DBM | null>
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Allows to access the DBM just before it's supposed to be saved to the DB.
|
|
87
|
+
*
|
|
88
|
+
* Normally does nothing.
|
|
89
|
+
*
|
|
90
|
+
* You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
|
|
91
|
+
* to pass it further.
|
|
92
|
+
*
|
|
93
|
+
* You can return `null` to prevent it from being saved, without throwing an error.
|
|
94
|
+
* `.save` method will then return the BM/DBM as it has entered the method (it **won't** return the null value!).
|
|
95
|
+
*
|
|
96
|
+
* You can do validations as needed here and throw errors, they will be propagated.
|
|
97
|
+
*/
|
|
98
|
+
beforeSave?: (dbm: DBM) => Promisable<DBM | null>
|
|
99
|
+
|
|
70
100
|
/**
|
|
71
101
|
* Called in:
|
|
72
102
|
* - dbmToBM (applied before DBM becomes BM)
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
pMap,
|
|
18
18
|
Promisable,
|
|
19
19
|
Saved,
|
|
20
|
+
SKIP,
|
|
21
|
+
UnixTimestampMillisNumber,
|
|
20
22
|
Unsaved,
|
|
21
23
|
ZodSchema,
|
|
22
24
|
ZodValidationError,
|
|
@@ -35,8 +37,6 @@ import {
|
|
|
35
37
|
transformLogProgress,
|
|
36
38
|
transformMap,
|
|
37
39
|
transformMapSimple,
|
|
38
|
-
transformMapSync,
|
|
39
|
-
transformTap,
|
|
40
40
|
writableVoid,
|
|
41
41
|
} from '@naturalcycles/nodejs-lib'
|
|
42
42
|
import { DBLibError } from '../cnst'
|
|
@@ -53,6 +53,7 @@ import { DBTransaction } from '../transaction/dbTransaction'
|
|
|
53
53
|
import {
|
|
54
54
|
CommonDaoCfg,
|
|
55
55
|
CommonDaoCreateOptions,
|
|
56
|
+
CommonDaoHooks,
|
|
56
57
|
CommonDaoLogLevel,
|
|
57
58
|
CommonDaoOptions,
|
|
58
59
|
CommonDaoSaveOptions,
|
|
@@ -99,7 +100,7 @@ export class CommonDao<
|
|
|
99
100
|
anonymize: dbm => dbm,
|
|
100
101
|
onValidationError: err => err,
|
|
101
102
|
...cfg.hooks,
|
|
102
|
-
},
|
|
103
|
+
} satisfies Partial<CommonDaoHooks<BM, DBM, TM, ID>>,
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
if (this.cfg.createId) {
|
|
@@ -131,7 +132,10 @@ export class CommonDao<
|
|
|
131
132
|
const table = opt.table || this.cfg.table
|
|
132
133
|
const started = this.logStarted(op, table)
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
let dbm = (await this.cfg.db.getByIds<DBM>(table, [id]))[0]
|
|
136
|
+
if (dbm && !opt.raw && this.cfg.hooks!.afterLoad) {
|
|
137
|
+
dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
|
|
138
|
+
}
|
|
135
139
|
|
|
136
140
|
const bm = opt.raw ? (dbm as any) : await this.dbmToBM(dbm, opt)
|
|
137
141
|
this.logResult(started, op, bm, table)
|
|
@@ -161,6 +165,10 @@ export class CommonDao<
|
|
|
161
165
|
const table = opt.table || this.cfg.table
|
|
162
166
|
const started = this.logStarted(op, table)
|
|
163
167
|
let [dbm] = await this.cfg.db.getByIds<DBM>(table, [id])
|
|
168
|
+
if (dbm && !opt.raw && this.cfg.hooks!.afterLoad) {
|
|
169
|
+
dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
if (!opt.raw) {
|
|
165
173
|
dbm = this.anyToDBM(dbm!, opt)
|
|
166
174
|
}
|
|
@@ -175,7 +183,11 @@ export class CommonDao<
|
|
|
175
183
|
const op = `getByIdAsTM(${id})`
|
|
176
184
|
const table = opt.table || this.cfg.table
|
|
177
185
|
const started = this.logStarted(op, table)
|
|
178
|
-
|
|
186
|
+
let [dbm] = await this.cfg.db.getByIds<DBM>(table, [id])
|
|
187
|
+
if (dbm && !opt.raw && this.cfg.hooks!.afterLoad) {
|
|
188
|
+
dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
|
|
189
|
+
}
|
|
190
|
+
|
|
179
191
|
if (opt.raw) {
|
|
180
192
|
this.logResult(started, op, dbm, table)
|
|
181
193
|
return (dbm as any) || null
|
|
@@ -187,20 +199,34 @@ export class CommonDao<
|
|
|
187
199
|
}
|
|
188
200
|
|
|
189
201
|
async getByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<Saved<BM>[]> {
|
|
202
|
+
if (!ids.length) return []
|
|
190
203
|
const op = `getByIds ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
|
|
191
204
|
const table = opt.table || this.cfg.table
|
|
192
205
|
const started = this.logStarted(op, table)
|
|
193
|
-
|
|
206
|
+
let dbms = await this.cfg.db.getByIds<DBM>(table, ids)
|
|
207
|
+
if (!opt.raw && this.cfg.hooks!.afterLoad && dbms.length) {
|
|
208
|
+
dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
209
|
+
_isTruthy,
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
194
213
|
const bms = opt.raw ? (dbms as any) : await this.dbmsToBM(dbms, opt)
|
|
195
214
|
this.logResult(started, op, bms, table)
|
|
196
215
|
return bms
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
async getByIdsAsDBM(ids: ID[], opt: CommonDaoOptions = {}): Promise<DBM[]> {
|
|
219
|
+
if (!ids.length) return []
|
|
200
220
|
const op = `getByIdsAsDBM ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
|
|
201
221
|
const table = opt.table || this.cfg.table
|
|
202
222
|
const started = this.logStarted(op, table)
|
|
203
|
-
|
|
223
|
+
let dbms = await this.cfg.db.getByIds<DBM>(table, ids)
|
|
224
|
+
if (!opt.raw && this.cfg.hooks!.afterLoad && dbms.length) {
|
|
225
|
+
dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
226
|
+
_isTruthy,
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
204
230
|
this.logResult(started, op, dbms, table)
|
|
205
231
|
return dbms
|
|
206
232
|
}
|
|
@@ -323,8 +349,14 @@ export class CommonDao<
|
|
|
323
349
|
q.table = opt.table || q.table
|
|
324
350
|
const op = `runQuery(${q.pretty()})`
|
|
325
351
|
const started = this.logStarted(op, q.table)
|
|
326
|
-
|
|
352
|
+
let { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
|
|
327
353
|
const partialQuery = !!q._selectedFieldNames
|
|
354
|
+
if (!opt.raw && this.cfg.hooks!.afterLoad && rows.length) {
|
|
355
|
+
rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
356
|
+
_isTruthy,
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
328
360
|
const bms = partialQuery || opt.raw ? (rows as any[]) : await this.dbmsToBM(rows, opt)
|
|
329
361
|
this.logResult(started, op, bms, q.table)
|
|
330
362
|
return {
|
|
@@ -345,7 +377,13 @@ export class CommonDao<
|
|
|
345
377
|
q.table = opt.table || q.table
|
|
346
378
|
const op = `runQueryAsDBM(${q.pretty()})`
|
|
347
379
|
const started = this.logStarted(op, q.table)
|
|
348
|
-
|
|
380
|
+
let { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
|
|
381
|
+
if (!opt.raw && this.cfg.hooks!.afterLoad && rows.length) {
|
|
382
|
+
rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
383
|
+
_isTruthy,
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
349
387
|
const partialQuery = !!q._selectedFieldNames
|
|
350
388
|
const dbms = partialQuery || opt.raw ? rows : this.anyToDBMs(rows, opt)
|
|
351
389
|
this.logResult(started, op, dbms, q.table)
|
|
@@ -364,7 +402,13 @@ export class CommonDao<
|
|
|
364
402
|
q.table = opt.table || q.table
|
|
365
403
|
const op = `runQueryAsTM(${q.pretty()})`
|
|
366
404
|
const started = this.logStarted(op, q.table)
|
|
367
|
-
|
|
405
|
+
let { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
|
|
406
|
+
if (!opt.raw && this.cfg.hooks!.afterLoad && rows.length) {
|
|
407
|
+
rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
408
|
+
_isTruthy,
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
368
412
|
const partialQuery = !!q._selectedFieldNames
|
|
369
413
|
const tms =
|
|
370
414
|
partialQuery || opt.raw ? (rows as any[]) : this.bmsToTM(await this.dbmsToBM(rows, opt), opt)
|
|
@@ -403,12 +447,17 @@ export class CommonDao<
|
|
|
403
447
|
|
|
404
448
|
await _pipeline([
|
|
405
449
|
this.cfg.db.streamQuery<DBM>(q, opt),
|
|
406
|
-
// optimization: 1 validation is enough
|
|
407
|
-
// transformMap<any, DBM>(dbm => (partialQuery || opt.raw ? dbm : this.anyToDBM(dbm, opt)), opt),
|
|
408
450
|
transformMap<DBM, Saved<BM>>(
|
|
409
451
|
async dbm => {
|
|
410
452
|
count++
|
|
411
|
-
|
|
453
|
+
if (partialQuery || opt.raw) return dbm as any
|
|
454
|
+
|
|
455
|
+
if (this.cfg.hooks!.afterLoad) {
|
|
456
|
+
dbm = (await this.cfg.hooks!.afterLoad(dbm)) as DBM
|
|
457
|
+
if (dbm === null) return SKIP
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return await this.dbmToBM(dbm, opt)
|
|
412
461
|
},
|
|
413
462
|
{
|
|
414
463
|
errorMode: opt.errorMode,
|
|
@@ -448,10 +497,17 @@ export class CommonDao<
|
|
|
448
497
|
|
|
449
498
|
await _pipeline([
|
|
450
499
|
this.cfg.db.streamQuery<any>(q, opt),
|
|
451
|
-
|
|
452
|
-
dbm => {
|
|
500
|
+
transformMap<any, DBM>(
|
|
501
|
+
async dbm => {
|
|
453
502
|
count++
|
|
454
|
-
|
|
503
|
+
if (partialQuery || opt.raw) return dbm
|
|
504
|
+
|
|
505
|
+
if (this.cfg.hooks!.afterLoad) {
|
|
506
|
+
dbm = (await this.cfg.hooks!.afterLoad(dbm)) as DBM
|
|
507
|
+
if (dbm === null) return SKIP
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return this.anyToDBM(dbm, opt)
|
|
455
511
|
},
|
|
456
512
|
{
|
|
457
513
|
errorMode: opt.errorMode,
|
|
@@ -491,9 +547,19 @@ export class CommonDao<
|
|
|
491
547
|
return stream
|
|
492
548
|
.on('error', err => stream.emit('error', err))
|
|
493
549
|
.pipe(
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
550
|
+
transformMap<any, DBM>(
|
|
551
|
+
async dbm => {
|
|
552
|
+
if (this.cfg.hooks!.afterLoad) {
|
|
553
|
+
dbm = (await this.cfg.hooks!.afterLoad(dbm)) as DBM
|
|
554
|
+
if (dbm === null) return SKIP
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return this.anyToDBM(dbm, opt)
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
errorMode: opt.errorMode,
|
|
561
|
+
},
|
|
562
|
+
),
|
|
497
563
|
)
|
|
498
564
|
}
|
|
499
565
|
|
|
@@ -523,9 +589,19 @@ export class CommonDao<
|
|
|
523
589
|
// .pipe(transformMap<DBM, Saved<BM>>(dbm => this.dbmToBM(dbm, opt), safeOpt))
|
|
524
590
|
.on('error', err => stream.emit('error', err))
|
|
525
591
|
.pipe(
|
|
526
|
-
transformMap<DBM, Saved<BM>>(
|
|
527
|
-
|
|
528
|
-
|
|
592
|
+
transformMap<DBM, Saved<BM>>(
|
|
593
|
+
async dbm => {
|
|
594
|
+
if (this.cfg.hooks!.afterLoad) {
|
|
595
|
+
dbm = (await this.cfg.hooks!.afterLoad(dbm)) as DBM
|
|
596
|
+
if (dbm === null) return SKIP
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return await this.dbmToBM(dbm, opt)
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
errorMode: opt.errorMode,
|
|
603
|
+
},
|
|
604
|
+
),
|
|
529
605
|
)
|
|
530
606
|
// this can make the stream async-iteration-friendly
|
|
531
607
|
// but not applying it now for perf reasons
|
|
@@ -569,8 +645,10 @@ export class CommonDao<
|
|
|
569
645
|
|
|
570
646
|
await _pipeline([
|
|
571
647
|
this.cfg.db.streamQuery<DBM>(q.select(['id']), opt),
|
|
572
|
-
transformMapSimple<DBM, ID>(objectWithId =>
|
|
573
|
-
|
|
648
|
+
transformMapSimple<DBM, ID>(objectWithId => {
|
|
649
|
+
count++
|
|
650
|
+
return objectWithId.id
|
|
651
|
+
}),
|
|
574
652
|
transformMap<ID, void>(mapper, {
|
|
575
653
|
...opt,
|
|
576
654
|
predicate: _passthroughPredicate,
|
|
@@ -618,8 +696,10 @@ export class CommonDao<
|
|
|
618
696
|
save: async (
|
|
619
697
|
bm: Unsaved<BM>,
|
|
620
698
|
opt: CommonDaoSaveOptions<DBM> = {},
|
|
621
|
-
): Promise<DBSaveBatchOperation> => {
|
|
622
|
-
|
|
699
|
+
): Promise<DBSaveBatchOperation | undefined> => {
|
|
700
|
+
// .save actually returns DBM (not BM) when it detects `opt.tx === true`
|
|
701
|
+
const row: DBM | null = (await this.save(bm, { ...opt, tx: true })) as any
|
|
702
|
+
if (row === null) return
|
|
623
703
|
|
|
624
704
|
return {
|
|
625
705
|
type: 'saveBatch',
|
|
@@ -635,8 +715,8 @@ export class CommonDao<
|
|
|
635
715
|
bms: Unsaved<BM>[],
|
|
636
716
|
opt: CommonDaoSaveOptions<DBM> = {},
|
|
637
717
|
): Promise<DBSaveBatchOperation | undefined> => {
|
|
638
|
-
if (!bms.length) return
|
|
639
718
|
const rows: DBM[] = (await this.saveBatch(bms, { ...opt, tx: true })) as any
|
|
719
|
+
if (!rows.length) return
|
|
640
720
|
|
|
641
721
|
return {
|
|
642
722
|
type: 'saveBatch',
|
|
@@ -682,9 +762,15 @@ export class CommonDao<
|
|
|
682
762
|
this.requireWriteAccess()
|
|
683
763
|
const idWasGenerated = !bm.id && this.cfg.createId
|
|
684
764
|
this.assignIdCreatedUpdated(bm, opt) // mutates
|
|
685
|
-
|
|
765
|
+
let dbm = await this.bmToDBM(bm as BM, opt)
|
|
766
|
+
|
|
767
|
+
if (this.cfg.hooks!.beforeSave) {
|
|
768
|
+
dbm = (await this.cfg.hooks!.beforeSave(dbm)) as DBM
|
|
769
|
+
if (dbm === null && !opt.tx) return bm as any
|
|
770
|
+
}
|
|
686
771
|
|
|
687
772
|
if (opt.tx) {
|
|
773
|
+
// May return `null`, in which case it'll be skipped
|
|
688
774
|
return dbm as any
|
|
689
775
|
}
|
|
690
776
|
|
|
@@ -697,6 +783,7 @@ export class CommonDao<
|
|
|
697
783
|
const started = this.logSaveStarted(op, bm, table)
|
|
698
784
|
const { excludeFromIndexes } = this.cfg
|
|
699
785
|
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
|
|
786
|
+
|
|
700
787
|
await this.cfg.db.saveBatch(table, [dbm], {
|
|
701
788
|
excludeFromIndexes,
|
|
702
789
|
assignGeneratedIds,
|
|
@@ -763,6 +850,12 @@ export class CommonDao<
|
|
|
763
850
|
const started = this.logSaveStarted(op, row, table)
|
|
764
851
|
const { excludeFromIndexes } = this.cfg
|
|
765
852
|
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
|
|
853
|
+
|
|
854
|
+
if (this.cfg.hooks!.beforeSave) {
|
|
855
|
+
row = (await this.cfg.hooks!.beforeSave(row)) as DBM
|
|
856
|
+
if (row === null) return dbm
|
|
857
|
+
}
|
|
858
|
+
|
|
766
859
|
await this.cfg.db.saveBatch(table, [row], {
|
|
767
860
|
excludeFromIndexes,
|
|
768
861
|
assignGeneratedIds,
|
|
@@ -778,10 +871,17 @@ export class CommonDao<
|
|
|
778
871
|
}
|
|
779
872
|
|
|
780
873
|
async saveBatch(bms: Unsaved<BM>[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>[]> {
|
|
874
|
+
if (!bms.length) return []
|
|
781
875
|
this.requireWriteAccess()
|
|
782
876
|
const table = opt.table || this.cfg.table
|
|
783
877
|
bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt))
|
|
784
|
-
|
|
878
|
+
let dbms = await this.bmsToDBM(bms as BM[], opt)
|
|
879
|
+
|
|
880
|
+
if (this.cfg.hooks!.beforeSave && dbms.length) {
|
|
881
|
+
dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.beforeSave!(dbm))).filter(
|
|
882
|
+
_isTruthy,
|
|
883
|
+
)
|
|
884
|
+
}
|
|
785
885
|
|
|
786
886
|
if (opt.tx) {
|
|
787
887
|
return dbms as any
|
|
@@ -802,6 +902,7 @@ export class CommonDao<
|
|
|
802
902
|
const started = this.logSaveStarted(op, bms, table)
|
|
803
903
|
const { excludeFromIndexes } = this.cfg
|
|
804
904
|
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
|
|
905
|
+
|
|
805
906
|
await this.cfg.db.saveBatch(table, dbms, {
|
|
806
907
|
excludeFromIndexes,
|
|
807
908
|
assignGeneratedIds,
|
|
@@ -818,6 +919,7 @@ export class CommonDao<
|
|
|
818
919
|
}
|
|
819
920
|
|
|
820
921
|
async saveBatchAsDBM(dbms: DBM[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<DBM[]> {
|
|
922
|
+
if (!dbms.length) return []
|
|
821
923
|
this.requireWriteAccess()
|
|
822
924
|
const table = opt.table || this.cfg.table
|
|
823
925
|
let rows = dbms
|
|
@@ -840,6 +942,12 @@ export class CommonDao<
|
|
|
840
942
|
const { excludeFromIndexes } = this.cfg
|
|
841
943
|
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
|
|
842
944
|
|
|
945
|
+
if (this.cfg.hooks!.beforeSave && rows.length) {
|
|
946
|
+
rows = (await pMap(rows, async row => await this.cfg.hooks!.beforeSave!(row))).filter(
|
|
947
|
+
_isTruthy,
|
|
948
|
+
)
|
|
949
|
+
}
|
|
950
|
+
|
|
843
951
|
await this.cfg.db.saveBatch(table, rows, {
|
|
844
952
|
excludeFromIndexes,
|
|
845
953
|
assignGeneratedIds,
|
|
@@ -873,6 +981,7 @@ export class CommonDao<
|
|
|
873
981
|
}
|
|
874
982
|
|
|
875
983
|
async deleteByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<number> {
|
|
984
|
+
if (!ids.length) return 0
|
|
876
985
|
this.requireWriteAccess()
|
|
877
986
|
this.requireObjectMutability(opt)
|
|
878
987
|
const op = `deleteByIds(${ids.join(', ')})`
|
|
@@ -941,6 +1050,7 @@ export class CommonDao<
|
|
|
941
1050
|
}
|
|
942
1051
|
|
|
943
1052
|
async updateByIds(ids: ID[], patch: DBPatch<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
|
|
1053
|
+
if (!ids.length) return 0
|
|
944
1054
|
return await this.updateByQuery(this.query().filterIn('id', ids), patch, opt)
|
|
945
1055
|
}
|
|
946
1056
|
|
|
@@ -1187,14 +1297,14 @@ export class CommonDao<
|
|
|
1187
1297
|
this.cfg.logger?.log(`<< ${table}.${op} in ${_since(started)}`)
|
|
1188
1298
|
}
|
|
1189
1299
|
|
|
1190
|
-
protected logStarted(op: string, table: string, force = false):
|
|
1300
|
+
protected logStarted(op: string, table: string, force = false): UnixTimestampMillisNumber {
|
|
1191
1301
|
if (this.cfg.logStarted || force) {
|
|
1192
1302
|
this.cfg.logger?.log(`>> ${table}.${op}`)
|
|
1193
1303
|
}
|
|
1194
1304
|
return Date.now()
|
|
1195
1305
|
}
|
|
1196
1306
|
|
|
1197
|
-
protected logSaveStarted(op: string, items: any, table: string):
|
|
1307
|
+
protected logSaveStarted(op: string, items: any, table: string): UnixTimestampMillisNumber {
|
|
1198
1308
|
if (this.cfg.logStarted) {
|
|
1199
1309
|
const args: any[] = [`>> ${table}.${op}`]
|
|
1200
1310
|
if (Array.isArray(items)) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as fsp from 'node:fs/promises'
|
|
1
3
|
import { createGzip, ZlibOptions } from 'node:zlib'
|
|
2
4
|
import {
|
|
3
5
|
AppError,
|
|
@@ -16,9 +18,12 @@ import {
|
|
|
16
18
|
transformTap,
|
|
17
19
|
transformToNDJson,
|
|
18
20
|
_pipeline,
|
|
21
|
+
_ensureDirSync,
|
|
22
|
+
_pathExistsSync,
|
|
23
|
+
_ensureFileSync,
|
|
24
|
+
_writeJsonFile,
|
|
19
25
|
} from '@naturalcycles/nodejs-lib'
|
|
20
26
|
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
21
|
-
import * as fs from 'fs-extra'
|
|
22
27
|
import { CommonDB } from '../common.db'
|
|
23
28
|
import { DBQuery } from '../index'
|
|
24
29
|
|
|
@@ -156,7 +161,7 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
156
161
|
`>> ${dimWhite('dbPipelineBackup')} started in ${grey(outputDirPath)}...${sinceUpdatedStr}`,
|
|
157
162
|
)
|
|
158
163
|
|
|
159
|
-
|
|
164
|
+
_ensureDirSync(outputDirPath)
|
|
160
165
|
|
|
161
166
|
tables ||= await db.getTables()
|
|
162
167
|
|
|
@@ -176,20 +181,20 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
176
181
|
const filePath = `${outputDirPath}/${table}.ndjson` + (gzip ? '.gz' : '')
|
|
177
182
|
const schemaFilePath = `${outputDirPath}/${table}.schema.json`
|
|
178
183
|
|
|
179
|
-
if (protectFromOverwrite && (
|
|
184
|
+
if (protectFromOverwrite && _pathExistsSync(filePath)) {
|
|
180
185
|
throw new AppError(`dbPipelineBackup: output file exists: ${filePath}`)
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
const started = Date.now()
|
|
184
189
|
let rows = 0
|
|
185
190
|
|
|
186
|
-
|
|
191
|
+
_ensureFileSync(filePath)
|
|
187
192
|
|
|
188
193
|
console.log(`>> ${grey(filePath)} started...`)
|
|
189
194
|
|
|
190
195
|
if (emitSchemaFromDB) {
|
|
191
196
|
const schema = await db.getTableSchema(table)
|
|
192
|
-
await
|
|
197
|
+
await _writeJsonFile(schemaFilePath, schema, { spaces: 2 })
|
|
193
198
|
console.log(`>> ${grey(schemaFilePath)} saved (generated from DB)`)
|
|
194
199
|
}
|
|
195
200
|
|
|
@@ -214,7 +219,7 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
214
219
|
fs.createWriteStream(filePath),
|
|
215
220
|
])
|
|
216
221
|
|
|
217
|
-
const { size: sizeBytes } = await
|
|
222
|
+
const { size: sizeBytes } = await fsp.stat(filePath)
|
|
218
223
|
|
|
219
224
|
const stats = NDJsonStats.create({
|
|
220
225
|
tookMillis: Date.now() - started,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
1
2
|
import { createUnzip } from 'node:zlib'
|
|
2
3
|
import {
|
|
3
4
|
AsyncMapper,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
_passthroughMapper,
|
|
9
10
|
SavedDBEntity,
|
|
10
11
|
localTime,
|
|
12
|
+
JsonSchemaObject,
|
|
11
13
|
} from '@naturalcycles/js-lib'
|
|
12
14
|
import {
|
|
13
15
|
NDJsonStats,
|
|
@@ -23,9 +25,10 @@ import {
|
|
|
23
25
|
transformTap,
|
|
24
26
|
writableForEach,
|
|
25
27
|
_pipeline,
|
|
28
|
+
_ensureDirSync,
|
|
29
|
+
_readJsonFile,
|
|
26
30
|
} from '@naturalcycles/nodejs-lib'
|
|
27
31
|
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
28
|
-
import * as fs from 'fs-extra'
|
|
29
32
|
import { CommonDB } from '../common.db'
|
|
30
33
|
import { CommonDBSaveOptions } from '../index'
|
|
31
34
|
|
|
@@ -139,7 +142,7 @@ export async function dbPipelineRestore(opt: DBPipelineRestoreOptions): Promise<
|
|
|
139
142
|
`>> ${dimWhite('dbPipelineRestore')} started in ${grey(inputDirPath)}...${sinceUpdatedStr}`,
|
|
140
143
|
)
|
|
141
144
|
|
|
142
|
-
|
|
145
|
+
_ensureDirSync(inputDirPath)
|
|
143
146
|
|
|
144
147
|
const tablesToGzip = new Set<string>()
|
|
145
148
|
const sizeByTable: Record<string, number> = {}
|
|
@@ -179,7 +182,7 @@ export async function dbPipelineRestore(opt: DBPipelineRestoreOptions): Promise<
|
|
|
179
182
|
return
|
|
180
183
|
}
|
|
181
184
|
|
|
182
|
-
const schema = await
|
|
185
|
+
const schema = await _readJsonFile<JsonSchemaObject<any>>(schemaFilePath)
|
|
183
186
|
await db.createTable(table, schema, { dropIfExists: true })
|
|
184
187
|
})
|
|
185
188
|
}
|