@naturalcycles/db-lib 8.56.0 → 8.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commondao/common.dao.d.ts +9 -3
- package/dist/commondao/common.dao.js +26 -22
- package/dist/pipeline/dbPipelineBackup.d.ts +8 -5
- package/dist/pipeline/dbPipelineBackup.js +7 -3
- package/package.json +1 -1
- package/src/commondao/common.dao.ts +32 -25
- package/src/pipeline/dbPipelineBackup.ts +19 -10
|
@@ -111,10 +111,16 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
111
111
|
* 1.1 Creates the row (via this.create()) if it doesn't exist
|
|
112
112
|
* (this will cause a validation error if Patch has not enough data for the row to be valid).
|
|
113
113
|
* 2. Applies the patch on top of loaded data.
|
|
114
|
-
* 3. Saves (as fast as possible since the read) with the Patch applied.
|
|
114
|
+
* 3. Saves (as fast as possible since the read) with the Patch applied, but only if the data has changed.
|
|
115
115
|
*/
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
patchById(id: ID, patch: Partial<BM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<Saved<BM>>;
|
|
117
|
+
/**
|
|
118
|
+
* Same as patchById, but takes the whole object as input.
|
|
119
|
+
* This "whole object" is mutated with the patch and returned.
|
|
120
|
+
* Otherwise, similar behavior as patchById.
|
|
121
|
+
* It still loads the row from the DB.
|
|
122
|
+
*/
|
|
123
|
+
patch(bm: Saved<BM>, patch: Partial<BM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<Saved<BM>>;
|
|
118
124
|
saveAsDBM(dbm: DBM, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<DBM>;
|
|
119
125
|
saveBatch(bms: Unsaved<BM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<Saved<BM>[]>;
|
|
120
126
|
saveBatchAsDBM(dbms: DBM[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<DBM[]>;
|
|
@@ -620,19 +620,16 @@ class CommonDao {
|
|
|
620
620
|
* 1.1 Creates the row (via this.create()) if it doesn't exist
|
|
621
621
|
* (this will cause a validation error if Patch has not enough data for the row to be valid).
|
|
622
622
|
* 2. Applies the patch on top of loaded data.
|
|
623
|
-
* 3. Saves (as fast as possible since the read) with the Patch applied.
|
|
623
|
+
* 3. Saves (as fast as possible since the read) with the Patch applied, but only if the data has changed.
|
|
624
624
|
*/
|
|
625
|
-
async
|
|
626
|
-
const bm = await this.getById(id, opt);
|
|
625
|
+
async patchById(id, patch, opt = {}) {
|
|
627
626
|
let patched;
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
};
|
|
633
|
-
if ((0, js_lib_1._deepJsonEquals)(bm, patched)) {
|
|
627
|
+
const loaded = await this.getById(id, opt);
|
|
628
|
+
if (loaded) {
|
|
629
|
+
patched = { ...loaded, ...patch };
|
|
630
|
+
if ((0, js_lib_1._deepJsonEquals)(loaded, patched)) {
|
|
634
631
|
// Skipping the save operation, as data is the same
|
|
635
|
-
return
|
|
632
|
+
return patched;
|
|
636
633
|
}
|
|
637
634
|
}
|
|
638
635
|
else {
|
|
@@ -640,23 +637,30 @@ class CommonDao {
|
|
|
640
637
|
}
|
|
641
638
|
return await this.save(patched, opt);
|
|
642
639
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
640
|
+
/**
|
|
641
|
+
* Same as patchById, but takes the whole object as input.
|
|
642
|
+
* This "whole object" is mutated with the patch and returned.
|
|
643
|
+
* Otherwise, similar behavior as patchById.
|
|
644
|
+
* It still loads the row from the DB.
|
|
645
|
+
*/
|
|
646
|
+
async patch(bm, patch, opt = {}) {
|
|
647
|
+
(0, js_lib_1._assert)(bm.id, 'patch argument object should have an id', {
|
|
648
|
+
bm,
|
|
649
|
+
});
|
|
650
|
+
const loaded = await this.getById(bm.id, opt);
|
|
651
|
+
if (loaded) {
|
|
652
|
+
Object.assign(loaded, patch);
|
|
653
|
+
if ((0, js_lib_1._deepJsonEquals)(loaded, bm)) {
|
|
652
654
|
// Skipping the save operation, as data is the same
|
|
653
|
-
return
|
|
655
|
+
return bm;
|
|
654
656
|
}
|
|
657
|
+
// Make `bm` exactly the same as `loaded`
|
|
658
|
+
(0, js_lib_1._objectAssignExact)(bm, loaded);
|
|
655
659
|
}
|
|
656
660
|
else {
|
|
657
|
-
|
|
661
|
+
Object.assign(bm, patch);
|
|
658
662
|
}
|
|
659
|
-
return await this.
|
|
663
|
+
return await this.save(bm, opt);
|
|
660
664
|
}
|
|
661
665
|
async saveAsDBM(dbm, opt = {}) {
|
|
662
666
|
this.requireWriteAccess();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { ZlibOptions } from 'node:zlib';
|
|
3
|
-
import { AsyncMapper, ErrorMode } from '@naturalcycles/js-lib';
|
|
3
|
+
import { AsyncMapper, ErrorMode, UnixTimestampNumber, StringMap } from '@naturalcycles/js-lib';
|
|
4
4
|
import { NDJsonStats, TransformLogProgressOptions, TransformMapOptions } from '@naturalcycles/nodejs-lib';
|
|
5
5
|
import { CommonDB } from '../common.db';
|
|
6
6
|
export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
@@ -33,10 +33,13 @@ export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
|
33
33
|
limit?: number;
|
|
34
34
|
/**
|
|
35
35
|
* If set - will do "incremental backup" (not full), only for entities that updated >= `sinceUpdated`
|
|
36
|
-
*
|
|
37
|
-
* @default undefined
|
|
38
36
|
*/
|
|
39
|
-
sinceUpdated?:
|
|
37
|
+
sinceUpdated?: UnixTimestampNumber;
|
|
38
|
+
/**
|
|
39
|
+
* Map for each table a `sinceUpdated` timestamp, or `undefined`.
|
|
40
|
+
* If set - will do "incremental backup" (not full), only for entities that updated >= `sinceUpdated` (on a per table basis)
|
|
41
|
+
*/
|
|
42
|
+
sinceUpdatedPerTable?: StringMap<UnixTimestampNumber>;
|
|
40
43
|
/**
|
|
41
44
|
* Directory path to store dumped files. Will create `${tableName}.ndjson` (or .ndjson.gz if gzip=true) files.
|
|
42
45
|
* All parent directories will be created.
|
|
@@ -63,7 +66,7 @@ export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
|
63
66
|
* @default `{}`
|
|
64
67
|
* Default mappers will be "passthroughMapper" (pass all data as-is).
|
|
65
68
|
*/
|
|
66
|
-
mapperPerTable?:
|
|
69
|
+
mapperPerTable?: StringMap<AsyncMapper>;
|
|
67
70
|
/**
|
|
68
71
|
* You can alter default `transformMapOptions` here.
|
|
69
72
|
*
|
|
@@ -18,17 +18,21 @@ const index_1 = require("../index");
|
|
|
18
18
|
* Optionally you can provide mapperPerTable and @param transformMapOptions (one for all mappers) - it will run for each table.
|
|
19
19
|
*/
|
|
20
20
|
async function dbPipelineBackup(opt) {
|
|
21
|
-
const { db, concurrency = 16, limit = 0,
|
|
21
|
+
const { db, concurrency = 16, limit = 0, outputDirPath, protectFromOverwrite = false, zlibOptions, mapperPerTable = {}, transformMapOptions, errorMode = js_lib_1.ErrorMode.SUPPRESS, emitSchemaFromDB = false, sortObjects = false, } = 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
|
-
|
|
26
|
-
console.log(`>> ${(0, nodejs_lib_1.dimWhite)('dbPipelineBackup')} started in ${(0, nodejs_lib_1.grey)(outputDirPath)}...${sinceUpdatedStr}`);
|
|
25
|
+
console.log(`>> ${(0, nodejs_lib_1.dimWhite)('dbPipelineBackup')} started in ${(0, nodejs_lib_1.grey)(outputDirPath)}...`);
|
|
27
26
|
(0, nodejs_lib_1._ensureDirSync)(outputDirPath);
|
|
28
27
|
tables ||= await db.getTables();
|
|
29
28
|
console.log(`${(0, nodejs_lib_1.yellow)(tables.length)} ${(0, nodejs_lib_1.boldWhite)('table(s)')}:\n` + tables.join('\n'));
|
|
30
29
|
const statsPerTable = {};
|
|
31
30
|
await (0, js_lib_1.pMap)(tables, async (table) => {
|
|
31
|
+
const sinceUpdated = opt.sinceUpdatedPerTable?.[table] || opt.sinceUpdated;
|
|
32
|
+
const sinceUpdatedStr = sinceUpdated
|
|
33
|
+
? ' since ' + (0, nodejs_lib_1.grey)((0, js_lib_1.localTime)(sinceUpdated).toPretty())
|
|
34
|
+
: '';
|
|
35
|
+
console.log(`>> ${(0, nodejs_lib_1.grey)(table)}${sinceUpdatedStr}`);
|
|
32
36
|
let q = index_1.DBQuery.create(table).limit(limit);
|
|
33
37
|
if (sinceUpdated) {
|
|
34
38
|
q = q.filter('updated', '>=', sinceUpdated);
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
_filterNullishValues,
|
|
6
6
|
_filterUndefinedValues,
|
|
7
7
|
_isTruthy,
|
|
8
|
+
_objectAssignExact,
|
|
8
9
|
_passthroughPredicate,
|
|
9
10
|
_since,
|
|
10
11
|
_truncate,
|
|
@@ -844,25 +845,22 @@ export class CommonDao<
|
|
|
844
845
|
* 1.1 Creates the row (via this.create()) if it doesn't exist
|
|
845
846
|
* (this will cause a validation error if Patch has not enough data for the row to be valid).
|
|
846
847
|
* 2. Applies the patch on top of loaded data.
|
|
847
|
-
* 3. Saves (as fast as possible since the read) with the Patch applied.
|
|
848
|
+
* 3. Saves (as fast as possible since the read) with the Patch applied, but only if the data has changed.
|
|
848
849
|
*/
|
|
849
|
-
async
|
|
850
|
+
async patchById(
|
|
850
851
|
id: ID,
|
|
851
852
|
patch: Partial<BM>,
|
|
852
853
|
opt: CommonDaoSaveBatchOptions<DBM> = {},
|
|
853
854
|
): Promise<Saved<BM>> {
|
|
854
|
-
const bm = await this.getById(id, opt)
|
|
855
855
|
let patched: Saved<BM>
|
|
856
|
+
const loaded = await this.getById(id, opt)
|
|
856
857
|
|
|
857
|
-
if (
|
|
858
|
-
patched = {
|
|
859
|
-
...bm,
|
|
860
|
-
...patch,
|
|
861
|
-
}
|
|
858
|
+
if (loaded) {
|
|
859
|
+
patched = { ...loaded, ...patch }
|
|
862
860
|
|
|
863
|
-
if (_deepJsonEquals(
|
|
861
|
+
if (_deepJsonEquals(loaded, patched)) {
|
|
864
862
|
// Skipping the save operation, as data is the same
|
|
865
|
-
return
|
|
863
|
+
return patched
|
|
866
864
|
}
|
|
867
865
|
} else {
|
|
868
866
|
patched = this.create({ ...patch, id }, opt)
|
|
@@ -871,29 +869,38 @@ export class CommonDao<
|
|
|
871
869
|
return await this.save(patched, opt)
|
|
872
870
|
}
|
|
873
871
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
872
|
+
/**
|
|
873
|
+
* Same as patchById, but takes the whole object as input.
|
|
874
|
+
* This "whole object" is mutated with the patch and returned.
|
|
875
|
+
* Otherwise, similar behavior as patchById.
|
|
876
|
+
* It still loads the row from the DB.
|
|
877
|
+
*/
|
|
878
|
+
async patch(
|
|
879
|
+
bm: Saved<BM>,
|
|
880
|
+
patch: Partial<BM>,
|
|
877
881
|
opt: CommonDaoSaveBatchOptions<DBM> = {},
|
|
878
|
-
): Promise<
|
|
879
|
-
|
|
880
|
-
|
|
882
|
+
): Promise<Saved<BM>> {
|
|
883
|
+
_assert(bm.id, 'patch argument object should have an id', {
|
|
884
|
+
bm,
|
|
885
|
+
})
|
|
881
886
|
|
|
882
|
-
|
|
883
|
-
patched = {
|
|
884
|
-
...dbm,
|
|
885
|
-
...patch,
|
|
886
|
-
}
|
|
887
|
+
const loaded = await this.getById(bm.id, opt)
|
|
887
888
|
|
|
888
|
-
|
|
889
|
+
if (loaded) {
|
|
890
|
+
Object.assign(loaded, patch)
|
|
891
|
+
|
|
892
|
+
if (_deepJsonEquals(loaded, bm)) {
|
|
889
893
|
// Skipping the save operation, as data is the same
|
|
890
|
-
return
|
|
894
|
+
return bm
|
|
891
895
|
}
|
|
896
|
+
|
|
897
|
+
// Make `bm` exactly the same as `loaded`
|
|
898
|
+
_objectAssignExact(bm, loaded)
|
|
892
899
|
} else {
|
|
893
|
-
|
|
900
|
+
Object.assign(bm, patch)
|
|
894
901
|
}
|
|
895
902
|
|
|
896
|
-
return await this.
|
|
903
|
+
return await this.save(bm, opt)
|
|
897
904
|
}
|
|
898
905
|
|
|
899
906
|
async saveAsDBM(dbm: DBM, opt: CommonDaoSaveBatchOptions<DBM> = {}): Promise<DBM> {
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
pMap,
|
|
9
9
|
_passthroughMapper,
|
|
10
10
|
localTime,
|
|
11
|
+
UnixTimestampNumber,
|
|
12
|
+
StringMap,
|
|
11
13
|
} from '@naturalcycles/js-lib'
|
|
12
14
|
import {
|
|
13
15
|
NDJsonStats,
|
|
@@ -65,10 +67,14 @@ export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
|
65
67
|
|
|
66
68
|
/**
|
|
67
69
|
* If set - will do "incremental backup" (not full), only for entities that updated >= `sinceUpdated`
|
|
68
|
-
*
|
|
69
|
-
* @default undefined
|
|
70
70
|
*/
|
|
71
|
-
sinceUpdated?:
|
|
71
|
+
sinceUpdated?: UnixTimestampNumber
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Map for each table a `sinceUpdated` timestamp, or `undefined`.
|
|
75
|
+
* If set - will do "incremental backup" (not full), only for entities that updated >= `sinceUpdated` (on a per table basis)
|
|
76
|
+
*/
|
|
77
|
+
sinceUpdatedPerTable?: StringMap<UnixTimestampNumber>
|
|
72
78
|
|
|
73
79
|
/**
|
|
74
80
|
* Directory path to store dumped files. Will create `${tableName}.ndjson` (or .ndjson.gz if gzip=true) files.
|
|
@@ -100,7 +106,7 @@ export interface DBPipelineBackupOptions extends TransformLogProgressOptions {
|
|
|
100
106
|
* @default `{}`
|
|
101
107
|
* Default mappers will be "passthroughMapper" (pass all data as-is).
|
|
102
108
|
*/
|
|
103
|
-
mapperPerTable?:
|
|
109
|
+
mapperPerTable?: StringMap<AsyncMapper>
|
|
104
110
|
|
|
105
111
|
/**
|
|
106
112
|
* You can alter default `transformMapOptions` here.
|
|
@@ -143,7 +149,6 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
143
149
|
db,
|
|
144
150
|
concurrency = 16,
|
|
145
151
|
limit = 0,
|
|
146
|
-
sinceUpdated,
|
|
147
152
|
outputDirPath,
|
|
148
153
|
protectFromOverwrite = false,
|
|
149
154
|
zlibOptions,
|
|
@@ -158,11 +163,7 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
158
163
|
|
|
159
164
|
let { tables } = opt
|
|
160
165
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
console.log(
|
|
164
|
-
`>> ${dimWhite('dbPipelineBackup')} started in ${grey(outputDirPath)}...${sinceUpdatedStr}`,
|
|
165
|
-
)
|
|
166
|
+
console.log(`>> ${dimWhite('dbPipelineBackup')} started in ${grey(outputDirPath)}...`)
|
|
166
167
|
|
|
167
168
|
_ensureDirSync(outputDirPath)
|
|
168
169
|
|
|
@@ -175,6 +176,14 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
175
176
|
await pMap(
|
|
176
177
|
tables,
|
|
177
178
|
async table => {
|
|
179
|
+
const sinceUpdated = opt.sinceUpdatedPerTable?.[table] || opt.sinceUpdated
|
|
180
|
+
|
|
181
|
+
const sinceUpdatedStr = sinceUpdated
|
|
182
|
+
? ' since ' + grey(localTime(sinceUpdated).toPretty())
|
|
183
|
+
: ''
|
|
184
|
+
|
|
185
|
+
console.log(`>> ${grey(table)}${sinceUpdatedStr}`)
|
|
186
|
+
|
|
178
187
|
let q = DBQuery.create(table).limit(limit)
|
|
179
188
|
|
|
180
189
|
if (sinceUpdated) {
|