@naturalcycles/db-lib 8.36.1 → 8.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/cachedb/cache.db.d.ts +1 -1
- package/dist/adapter/cachedb/cache.db.model.d.ts +1 -1
- package/dist/adapter/file/file.db.d.ts +1 -1
- package/dist/adapter/file/file.db.js +1 -0
- package/dist/adapter/inmemory/inMemory.db.d.ts +1 -1
- package/dist/adapter/inmemory/inMemory.db.js +8 -8
- package/dist/base.common.db.d.ts +1 -1
- package/dist/common.db.d.ts +4 -1
- package/dist/commondao/common.dao.d.ts +5 -6
- package/dist/commondao/common.dao.js +28 -40
- package/dist/commondao/common.dao.model.d.ts +9 -8
- package/dist/db.model.d.ts +14 -1
- package/dist/index.d.ts +2 -2
- package/dist/testing/daoTest.js +1 -2
- package/dist/testing/dbTest.d.ts +2 -0
- package/dist/testing/dbTest.js +17 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/test.model.d.ts +1 -2
- package/dist/testing/test.model.js +1 -17
- package/package.json +1 -1
- package/src/adapter/cachedb/cache.db.model.ts +1 -1
- package/src/adapter/cachedb/cache.db.ts +1 -1
- package/src/adapter/file/file.db.ts +6 -3
- package/src/adapter/inmemory/inMemory.db.ts +13 -10
- package/src/base.common.db.ts +1 -1
- package/src/common.db.ts +4 -1
- package/src/commondao/common.dao.model.ts +9 -8
- package/src/commondao/common.dao.ts +37 -58
- package/src/db.model.ts +16 -1
- package/src/index.ts +2 -0
- package/src/testing/daoTest.ts +3 -2
- package/src/testing/dbTest.ts +24 -2
- package/src/testing/index.ts +4 -2
- package/src/testing/test.model.ts +1 -18
|
@@ -24,7 +24,7 @@ export declare class CacheDB extends BaseCommonDB implements CommonDB {
|
|
|
24
24
|
createTable<ROW extends ObjectWithId>(table: string, schema: JsonSchemaObject<ROW>, opt?: CacheDBCreateOptions): Promise<void>;
|
|
25
25
|
getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CacheDBSaveOptions<ROW>): Promise<ROW[]>;
|
|
26
26
|
deleteByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CacheDBOptions): Promise<number>;
|
|
27
|
-
saveBatch<ROW extends ObjectWithId
|
|
27
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], opt?: CacheDBSaveOptions<ROW>): Promise<void>;
|
|
28
28
|
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBSaveOptions<ROW>): Promise<RunQueryResult<ROW>>;
|
|
29
29
|
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBOptions): Promise<number>;
|
|
30
30
|
streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBStreamOptions): Readable;
|
|
@@ -47,7 +47,7 @@ export interface CacheDBOptions {
|
|
|
47
47
|
*/
|
|
48
48
|
onlyCache?: boolean;
|
|
49
49
|
}
|
|
50
|
-
export interface CacheDBSaveOptions<ROW extends ObjectWithId
|
|
50
|
+
export interface CacheDBSaveOptions<ROW extends Partial<ObjectWithId>> extends CacheDBOptions, CommonDBSaveOptions<ROW> {
|
|
51
51
|
}
|
|
52
52
|
export interface CacheDBStreamOptions extends CacheDBOptions, CommonDBStreamOptions {
|
|
53
53
|
}
|
|
@@ -22,7 +22,7 @@ export declare class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
22
22
|
ping(): Promise<void>;
|
|
23
23
|
getTables(): Promise<string[]>;
|
|
24
24
|
getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<ROW[]>;
|
|
25
|
-
saveBatch<ROW extends ObjectWithId
|
|
25
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
26
26
|
/**
|
|
27
27
|
* Implementation is optimized for loading/saving _whole files_.
|
|
28
28
|
*/
|
|
@@ -46,6 +46,7 @@ class FileDB extends __1.BaseCommonDB {
|
|
|
46
46
|
// 2. Merge with new data (using ids)
|
|
47
47
|
let saved = 0;
|
|
48
48
|
rows.forEach(r => {
|
|
49
|
+
(0, js_lib_1._assert)(r.id, 'FileDB: row.id is required');
|
|
49
50
|
if (!(0, js_lib_1._deepEquals)(byId[r.id], r)) {
|
|
50
51
|
byId[r.id] = r;
|
|
51
52
|
saved++;
|
|
@@ -53,7 +53,7 @@ export declare class InMemoryDB implements CommonDB {
|
|
|
53
53
|
getTableSchema<ROW extends ObjectWithId>(_table: string): Promise<JsonSchemaRootObject<ROW>>;
|
|
54
54
|
createTable<ROW extends ObjectWithId>(_table: string, _schema: JsonSchemaObject<ROW>, opt?: CommonDBCreateOptions): Promise<void>;
|
|
55
55
|
getByIds<ROW extends ObjectWithId>(_table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<ROW[]>;
|
|
56
|
-
saveBatch<ROW extends ObjectWithId
|
|
56
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(_table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
57
57
|
deleteByIds<ROW extends ObjectWithId>(_table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<number>;
|
|
58
58
|
deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
|
|
59
59
|
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
|
|
@@ -73,25 +73,25 @@ class InMemoryDB {
|
|
|
73
73
|
(_a = this.data)[table] || (_a[table] = {});
|
|
74
74
|
return ids.map(id => this.data[table][id]).filter(Boolean);
|
|
75
75
|
}
|
|
76
|
-
async saveBatch(_table, rows,
|
|
76
|
+
async saveBatch(_table, rows, opt = {}) {
|
|
77
77
|
var _a;
|
|
78
78
|
const table = this.cfg.tablesPrefix + _table;
|
|
79
79
|
(_a = this.data)[table] || (_a[table] = {});
|
|
80
80
|
rows.forEach(r => {
|
|
81
81
|
if (!r.id) {
|
|
82
82
|
this.cfg.logger?.warn({ rows });
|
|
83
|
-
throw new Error(`InMemoryDB
|
|
83
|
+
throw new Error(`InMemoryDB doesn't support id auto-generation in saveBatch, row without id was given`);
|
|
84
|
+
}
|
|
85
|
+
if (opt.saveMethod === 'insert' && this.data[table][r.id]) {
|
|
86
|
+
throw new Error(`InMemoryDB: INSERT failed, entity exists: ${table}.${r.id}`);
|
|
87
|
+
}
|
|
88
|
+
if (opt.saveMethod === 'update' && !this.data[table][r.id]) {
|
|
89
|
+
throw new Error(`InMemoryDB: UPDATE failed, entity doesn't exist: ${table}.${r.id}`);
|
|
84
90
|
}
|
|
85
91
|
// JSON parse/stringify (deep clone) is to:
|
|
86
92
|
// 1. Not store values "by reference" (avoid mutation bugs)
|
|
87
93
|
// 2. Simulate real DB that would do something like that in a transport layer anyway
|
|
88
94
|
this.data[table][r.id] = JSON.parse(JSON.stringify(r), nodejs_lib_1.bufferReviver);
|
|
89
|
-
// special treatment for Buffers (assign them raw, without JSON parse/stringify)
|
|
90
|
-
// Object.entries(r).forEach(([k, v]) => {
|
|
91
|
-
// if (Buffer.isBuffer(v)) {
|
|
92
|
-
// this.data[table]![r.id]![k] = v
|
|
93
|
-
// }
|
|
94
|
-
// })
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
async deleteByIds(_table, ids, _opt) {
|
package/dist/base.common.db.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare class BaseCommonDB implements CommonDB {
|
|
|
18
18
|
getByIds<ROW extends ObjectWithId>(_table: string, _ids: ROW['id'][]): Promise<ROW[]>;
|
|
19
19
|
runQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<RunQueryResult<ROW>>;
|
|
20
20
|
runQueryCount<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<number>;
|
|
21
|
-
saveBatch<ROW extends ObjectWithId
|
|
21
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(_table: string, _rows: ROW[], _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
22
22
|
streamQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): ReadableTyped<ROW>;
|
|
23
23
|
/**
|
|
24
24
|
* Naive implementation.
|
package/dist/common.db.d.ts
CHANGED
|
@@ -38,7 +38,10 @@ export interface CommonDB {
|
|
|
38
38
|
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
|
|
39
39
|
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions): Promise<number>;
|
|
40
40
|
streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBStreamOptions): ReadableTyped<ROW>;
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment).
|
|
43
|
+
*/
|
|
44
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
42
45
|
/**
|
|
43
46
|
* Returns number of deleted items.
|
|
44
47
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Saved } from '@naturalcycles/js-lib';
|
|
1
|
+
import { AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Saved, Unsaved } from '@naturalcycles/js-lib';
|
|
2
2
|
import { AjvSchema, ObjectSchemaTyped, ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
3
|
import { DBModelType, RunQueryResult } from '../db.model';
|
|
4
4
|
import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
|
|
@@ -35,6 +35,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
35
35
|
* Throws if readOnly is true
|
|
36
36
|
*/
|
|
37
37
|
private requireObjectMutability;
|
|
38
|
+
private ensureUniqueId;
|
|
38
39
|
getBy(by: keyof DBM, value: any, limit?: number, opt?: CommonDaoOptions): Promise<Saved<BM>[]>;
|
|
39
40
|
getOneBy(by: keyof DBM, value: any, opt?: CommonDaoOptions): Promise<Saved<BM> | null>;
|
|
40
41
|
getAll(opt?: CommonDaoOptions): Promise<Saved<BM>[]>;
|
|
@@ -81,13 +82,11 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
81
82
|
*/
|
|
82
83
|
assignIdCreatedUpdated(obj: DBM, opt?: CommonDaoOptions): DBM;
|
|
83
84
|
assignIdCreatedUpdated(obj: BM, opt?: CommonDaoOptions): Saved<BM>;
|
|
85
|
+
assignIdCreatedUpdated(obj: Unsaved<BM>, opt?: CommonDaoOptions): Saved<BM>;
|
|
84
86
|
/**
|
|
85
87
|
* Mutates with id, created, updated
|
|
86
88
|
*/
|
|
87
|
-
save(bm: BM
|
|
88
|
-
private ensureImmutableDontExist;
|
|
89
|
-
private ensureUniqueId;
|
|
90
|
-
private throwIfObjectExists;
|
|
89
|
+
save(bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>>;
|
|
91
90
|
/**
|
|
92
91
|
* Loads the row by id.
|
|
93
92
|
* Creates the row (via this.create()) if it doesn't exist
|
|
@@ -99,7 +98,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
99
98
|
patch(id: ID, patch: Partial<BM>, opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>>;
|
|
100
99
|
patchAsDBM(id: ID, patch: Partial<DBM>, opt?: CommonDaoSaveOptions<DBM>): Promise<DBM>;
|
|
101
100
|
saveAsDBM(dbm: DBM, opt?: CommonDaoSaveOptions<DBM>): Promise<DBM>;
|
|
102
|
-
saveBatch(bms: BM[], opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>[]>;
|
|
101
|
+
saveBatch(bms: Unsaved<BM>[], opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>[]>;
|
|
103
102
|
saveBatchAsDBM(dbms: DBM[], opt?: CommonDaoSaveOptions<DBM>): Promise<DBM[]>;
|
|
104
103
|
/**
|
|
105
104
|
* @returns number of deleted items
|
|
@@ -175,14 +175,25 @@ class CommonDao {
|
|
|
175
175
|
/**
|
|
176
176
|
* Throws if readOnly is true
|
|
177
177
|
*/
|
|
178
|
-
requireObjectMutability() {
|
|
179
|
-
if (this.cfg.immutable) {
|
|
178
|
+
requireObjectMutability(opt) {
|
|
179
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
180
180
|
throw new js_lib_1.AppError(cnst_1.DBLibError.OBJECT_IS_IMMUTABLE, {
|
|
181
181
|
code: cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
182
182
|
table: this.cfg.table,
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
+
async ensureUniqueId(table, dbm) {
|
|
187
|
+
// todo: retry N times
|
|
188
|
+
const existing = await this.cfg.db.getByIds(table, [dbm.id]);
|
|
189
|
+
if (existing.length) {
|
|
190
|
+
throw new js_lib_1.AppError(cnst_1.DBLibError.NON_UNIQUE_ID, {
|
|
191
|
+
table,
|
|
192
|
+
code: cnst_1.DBLibError.NON_UNIQUE_ID,
|
|
193
|
+
ids: existing.map(i => i.id),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
186
197
|
async getBy(by, value, limit = 0, opt) {
|
|
187
198
|
return await this.query().filterEq(by, value).limit(limit).runQuery(opt);
|
|
188
199
|
}
|
|
@@ -445,8 +456,9 @@ class CommonDao {
|
|
|
445
456
|
const table = opt.table || this.cfg.table;
|
|
446
457
|
if (opt.ensureUniqueId && idWasGenerated)
|
|
447
458
|
await this.ensureUniqueId(table, dbm);
|
|
448
|
-
if (this.cfg.immutable)
|
|
449
|
-
|
|
459
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
460
|
+
opt.saveMethod || (opt.saveMethod = 'insert');
|
|
461
|
+
}
|
|
450
462
|
const op = `save(${dbm.id})`;
|
|
451
463
|
const started = this.logSaveStarted(op, bm, table);
|
|
452
464
|
await this.cfg.db.saveBatch(table, [dbm], {
|
|
@@ -456,30 +468,6 @@ class CommonDao {
|
|
|
456
468
|
this.logSaveResult(started, op, table);
|
|
457
469
|
return bm;
|
|
458
470
|
}
|
|
459
|
-
async ensureImmutableDontExist(table, ids) {
|
|
460
|
-
await this.throwIfObjectExists(table, ids, [
|
|
461
|
-
cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
462
|
-
{
|
|
463
|
-
code: cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
464
|
-
table,
|
|
465
|
-
},
|
|
466
|
-
]);
|
|
467
|
-
}
|
|
468
|
-
async ensureUniqueId(table, dbm) {
|
|
469
|
-
// todo: retry N times
|
|
470
|
-
await this.throwIfObjectExists(table, [dbm.id], [
|
|
471
|
-
cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
472
|
-
{
|
|
473
|
-
code: cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
474
|
-
table,
|
|
475
|
-
},
|
|
476
|
-
]);
|
|
477
|
-
}
|
|
478
|
-
async throwIfObjectExists(table, ids, errorMeta) {
|
|
479
|
-
const existing = await this.cfg.db.getByIds(table, ids);
|
|
480
|
-
if (existing.length > 0)
|
|
481
|
-
throw new js_lib_1.AppError(errorMeta[0], { ...errorMeta[1], ids: existing.map(i => i.id) });
|
|
482
|
-
}
|
|
483
471
|
/**
|
|
484
472
|
* Loads the row by id.
|
|
485
473
|
* Creates the row (via this.create()) if it doesn't exist
|
|
@@ -513,8 +501,9 @@ class CommonDao {
|
|
|
513
501
|
dbm = this.anyToDBM(dbm, opt);
|
|
514
502
|
if (opt.ensureUniqueId && idWasGenerated)
|
|
515
503
|
await this.ensureUniqueId(table, dbm);
|
|
516
|
-
|
|
517
|
-
|
|
504
|
+
}
|
|
505
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
506
|
+
opt.saveMethod || (opt.saveMethod = 'insert');
|
|
518
507
|
}
|
|
519
508
|
const op = `saveAsDBM(${dbm.id})`;
|
|
520
509
|
const started = this.logSaveStarted(op, dbm, table);
|
|
@@ -532,8 +521,9 @@ class CommonDao {
|
|
|
532
521
|
const dbms = await this.bmsToDBM(bms, opt);
|
|
533
522
|
if (opt.ensureUniqueId)
|
|
534
523
|
throw new js_lib_1.AppError('ensureUniqueId is not supported in saveBatch');
|
|
535
|
-
if (this.cfg.immutable)
|
|
536
|
-
|
|
524
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
525
|
+
opt.saveMethod || (opt.saveMethod = 'insert');
|
|
526
|
+
}
|
|
537
527
|
const op = `saveBatch ${dbms.length} row(s) (${(0, js_lib_1._truncate)(dbms
|
|
538
528
|
.slice(0, 10)
|
|
539
529
|
.map(bm => bm.id)
|
|
@@ -554,8 +544,9 @@ class CommonDao {
|
|
|
554
544
|
dbms = this.anyToDBMs(dbms, opt);
|
|
555
545
|
if (opt.ensureUniqueId)
|
|
556
546
|
throw new js_lib_1.AppError('ensureUniqueId is not supported in saveBatch');
|
|
557
|
-
|
|
558
|
-
|
|
547
|
+
}
|
|
548
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
549
|
+
opt.saveMethod || (opt.saveMethod = 'insert');
|
|
559
550
|
}
|
|
560
551
|
const op = `saveBatchAsDBM ${dbms.length} row(s) (${(0, js_lib_1._truncate)(dbms
|
|
561
552
|
.slice(0, 10)
|
|
@@ -573,8 +564,7 @@ class CommonDao {
|
|
|
573
564
|
if (!id)
|
|
574
565
|
return 0;
|
|
575
566
|
this.requireWriteAccess();
|
|
576
|
-
|
|
577
|
-
this.requireObjectMutability();
|
|
567
|
+
this.requireObjectMutability(opt);
|
|
578
568
|
const op = `deleteById(${id})`;
|
|
579
569
|
const table = opt.table || this.cfg.table;
|
|
580
570
|
const started = this.logStarted(op, table);
|
|
@@ -584,8 +574,7 @@ class CommonDao {
|
|
|
584
574
|
}
|
|
585
575
|
async deleteByIds(ids, opt = {}) {
|
|
586
576
|
this.requireWriteAccess();
|
|
587
|
-
|
|
588
|
-
this.requireObjectMutability();
|
|
577
|
+
this.requireObjectMutability(opt);
|
|
589
578
|
const op = `deleteByIds(${ids.join(', ')})`;
|
|
590
579
|
const table = opt.table || this.cfg.table;
|
|
591
580
|
const started = this.logStarted(op, table);
|
|
@@ -600,8 +589,7 @@ class CommonDao {
|
|
|
600
589
|
*/
|
|
601
590
|
async deleteByQuery(q, opt = {}) {
|
|
602
591
|
this.requireWriteAccess();
|
|
603
|
-
|
|
604
|
-
this.requireObjectMutability();
|
|
592
|
+
this.requireObjectMutability(opt);
|
|
605
593
|
q.table = opt.table || q.table;
|
|
606
594
|
const op = `deleteByQuery(${q.pretty()})`;
|
|
607
595
|
const started = this.logStarted(op, q.table);
|
|
@@ -49,17 +49,18 @@ export interface CommonDaoCfg<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
49
49
|
tmSchema?: ObjectSchemaTyped<TM> | AjvSchema<TM>;
|
|
50
50
|
excludeFromIndexes?: (keyof DBM)[];
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* * saveBatch, delete*() and patch() will throw.
|
|
52
|
+
* Defaults to false.
|
|
53
|
+
* Setting it to true will set saveMethod to `insert` for save/saveBatch, which will
|
|
54
|
+
* fail for rows that already exist in the DB (if CommonDB implementation supports it).
|
|
56
55
|
*
|
|
57
|
-
*
|
|
56
|
+
* `delete*` and `patch` will throw.
|
|
57
|
+
*
|
|
58
|
+
* You can still override saveMethod, or set opt.allowMutability to allow deletion.
|
|
58
59
|
*/
|
|
59
60
|
immutable?: boolean;
|
|
60
61
|
/**
|
|
61
|
-
*
|
|
62
|
-
* Set to true to limit DB writing (will throw an error
|
|
62
|
+
* Defaults to false.
|
|
63
|
+
* Set to true to limit DB writing (will throw an error in such case).
|
|
63
64
|
*/
|
|
64
65
|
readOnly?: boolean;
|
|
65
66
|
/**
|
|
@@ -165,7 +166,7 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
165
166
|
/**
|
|
166
167
|
* All properties default to undefined.
|
|
167
168
|
*/
|
|
168
|
-
export interface CommonDaoSaveOptions<DBM extends ObjectWithId
|
|
169
|
+
export interface CommonDaoSaveOptions<DBM extends Partial<ObjectWithId>> extends CommonDaoOptions, CommonDBSaveOptions<DBM> {
|
|
169
170
|
/**
|
|
170
171
|
* @default false
|
|
171
172
|
*
|
package/dist/db.model.d.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { AnyObjectWithId, ObjectWithId } from '@naturalcycles/js-lib';
|
|
2
|
+
/**
|
|
3
|
+
* Similar to SQL INSERT, UPDATE.
|
|
4
|
+
* Insert will fail if row already exists.
|
|
5
|
+
* Update will fail if row is missing.
|
|
6
|
+
* Upsert will auto-detect and use Insert or Update to not fail.
|
|
7
|
+
*
|
|
8
|
+
* Default is Upsert.
|
|
9
|
+
*/
|
|
10
|
+
export declare type CommonDBSaveMethod = 'upsert' | 'insert' | 'update';
|
|
2
11
|
export interface CommonDBOptions {
|
|
3
12
|
}
|
|
4
13
|
/**
|
|
5
14
|
* All properties default to undefined.
|
|
6
15
|
*/
|
|
7
|
-
export interface CommonDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId> extends CommonDBOptions {
|
|
16
|
+
export interface CommonDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObjectWithId> extends CommonDBOptions {
|
|
8
17
|
excludeFromIndexes?: (keyof ROW)[];
|
|
18
|
+
/**
|
|
19
|
+
* Default is `upsert`
|
|
20
|
+
*/
|
|
21
|
+
saveMethod?: CommonDBSaveMethod;
|
|
9
22
|
}
|
|
10
23
|
export declare type CommonDBStreamOptions = CommonDBOptions;
|
|
11
24
|
export interface CommonDBCreateOptions extends CommonDBOptions {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { DBLibError } from './cnst';
|
|
|
6
6
|
import { CommonDB } from './common.db';
|
|
7
7
|
import { CommonDao } from './commondao/common.dao';
|
|
8
8
|
import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoLogLevel, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoHooks } from './commondao/common.dao.model';
|
|
9
|
-
import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, DBDeleteByIdsOperation, DBModelType, DBOperation, DBRelation, DBSaveBatchOperation, RunQueryResult } from './db.model';
|
|
9
|
+
import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveMethod, CommonDBSaveOptions, CommonDBStreamOptions, DBDeleteByIdsOperation, DBModelType, DBOperation, DBRelation, DBSaveBatchOperation, RunQueryResult } from './db.model';
|
|
10
10
|
import { CommonKeyValueDao, CommonKeyValueDaoCfg } from './kv/commonKeyValueDao';
|
|
11
11
|
import { CommonKeyValueDB, KeyValueDBTuple } from './kv/commonKeyValueDB';
|
|
12
12
|
import { createdUpdatedFields, createdUpdatedIdFields, deserializeJsonField, serializeJsonField } from './model.util';
|
|
@@ -17,5 +17,5 @@ import { DBQuery, DBQueryFilter, DBQueryFilterOperator, dbQueryFilterOperatorVal
|
|
|
17
17
|
import { DBTransaction, RunnableDBTransaction } from './transaction/dbTransaction';
|
|
18
18
|
import { commitDBTransactionSimple, mergeDBOperations } from './transaction/dbTransaction.util';
|
|
19
19
|
export * from './kv/commonKeyValueDaoMemoCache';
|
|
20
|
-
export type { DBQueryFilterOperator, DBQueryFilter, DBQueryOrder, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoHooks, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBCreateOptions, CommonDB, RunQueryResult, CommonDaoCfg, InMemoryDBCfg, InMemoryKeyValueDBCfg, DBPipelineBackupOptions, DBPipelineRestoreOptions, DBPipelineCopyOptions, DBOperation, DBSaveBatchOperation, DBDeleteByIdsOperation, CommonKeyValueDB, CommonKeyValueDaoCfg, KeyValueDBTuple, };
|
|
20
|
+
export type { DBQueryFilterOperator, DBQueryFilter, DBQueryOrder, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoHooks, CommonDBOptions, CommonDBSaveOptions, CommonDBSaveMethod, CommonDBStreamOptions, CommonDBCreateOptions, CommonDB, RunQueryResult, CommonDaoCfg, InMemoryDBCfg, InMemoryKeyValueDBCfg, DBPipelineBackupOptions, DBPipelineRestoreOptions, DBPipelineCopyOptions, DBOperation, DBSaveBatchOperation, DBDeleteByIdsOperation, CommonKeyValueDB, CommonKeyValueDaoCfg, KeyValueDBTuple, };
|
|
21
21
|
export { DBQuery, dbQueryFilterOperatorValues, RunnableDBQuery, CommonDaoLogLevel, DBRelation, DBModelType, CommonDao, createdUpdatedFields, createdUpdatedIdFields, InMemoryDB, InMemoryKeyValueDB, queryInMemory, serializeJsonField, deserializeJsonField, dbPipelineBackup, dbPipelineRestore, dbPipelineCopy, DBLibError, BaseCommonDB, DBTransaction, RunnableDBTransaction, mergeDBOperations, commitDBTransactionSimple, CommonKeyValueDao, };
|
package/dist/testing/daoTest.js
CHANGED
|
@@ -7,7 +7,6 @@ const __1 = require("..");
|
|
|
7
7
|
const common_dao_1 = require("../commondao/common.dao");
|
|
8
8
|
const dbTest_1 = require("./dbTest");
|
|
9
9
|
const test_model_1 = require("./test.model");
|
|
10
|
-
const _1 = require(".");
|
|
11
10
|
function runCommonDaoTest(db, features = {}, quirks = {}) {
|
|
12
11
|
const dao = new common_dao_1.CommonDao({
|
|
13
12
|
table: test_model_1.TEST_TABLE,
|
|
@@ -42,7 +41,7 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
|
|
|
42
41
|
// CREATE TABLE, DROP
|
|
43
42
|
if (createTable) {
|
|
44
43
|
test('createTable, dropIfExists=true', async () => {
|
|
45
|
-
await dao.createTable(
|
|
44
|
+
await dao.createTable(test_model_1.testItemDBMJsonSchema.build(), { dropIfExists: true });
|
|
46
45
|
});
|
|
47
46
|
}
|
|
48
47
|
if (querying) {
|
package/dist/testing/dbTest.d.ts
CHANGED
package/dist/testing/dbTest.js
CHANGED
|
@@ -12,7 +12,7 @@ const test_util_1 = require("./test.util");
|
|
|
12
12
|
function runCommonDBTest(db, features = {}, quirks = {}) {
|
|
13
13
|
const { querying = true, tableSchemas = true, createTable = true, dbQueryFilter = true,
|
|
14
14
|
// dbQueryFilterIn = true,
|
|
15
|
-
dbQueryOrder = true, dbQuerySelectFields = true, streaming = true, strongConsistency = true, bufferSupport = true, nullValues = true, documentDB = true, } = features;
|
|
15
|
+
dbQueryOrder = true, dbQuerySelectFields = true, insert = true, update = true, streaming = true, strongConsistency = true, bufferSupport = true, nullValues = true, documentDB = true, } = features;
|
|
16
16
|
// const {
|
|
17
17
|
// allowExtraPropertiesInResponse,
|
|
18
18
|
// allowBooleansAsUndefined,
|
|
@@ -28,7 +28,7 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
|
|
|
28
28
|
// CREATE TABLE, DROP
|
|
29
29
|
if (createTable) {
|
|
30
30
|
test('createTable, dropIfExists=true', async () => {
|
|
31
|
-
await db.createTable(test_model_1.TEST_TABLE,
|
|
31
|
+
await db.createTable(test_model_1.TEST_TABLE, test_model_1.testItemDBMJsonSchema.build(), { dropIfExists: true });
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
if (querying) {
|
|
@@ -87,9 +87,24 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
|
|
|
87
87
|
expect(Object.keys(item3Loaded)).not.toContain('k2');
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
+
if (update) {
|
|
91
|
+
test('saveBatch UPDATE method should throw', async () => {
|
|
92
|
+
await expect(db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'update' })).rejects.toThrow();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
90
95
|
test('saveBatch test items', async () => {
|
|
91
96
|
await db.saveBatch(test_model_1.TEST_TABLE, items);
|
|
92
97
|
});
|
|
98
|
+
if (insert) {
|
|
99
|
+
test('saveBatch INSERT method should throw', async () => {
|
|
100
|
+
await expect(db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'insert' })).rejects.toThrow();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (update) {
|
|
104
|
+
test('saveBatch UPDATE method should pass', async () => {
|
|
105
|
+
await db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'update' });
|
|
106
|
+
});
|
|
107
|
+
}
|
|
93
108
|
// GET not empty
|
|
94
109
|
test('getByIds all items', async () => {
|
|
95
110
|
const rows = await db.getByIds(test_model_1.TEST_TABLE, items.map(i => i.id).concat('abcd'));
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runCommonDaoTest } from './daoTest';
|
|
2
2
|
import { CommonDBImplementationFeatures, CommonDBImplementationQuirks, runCommonDBTest } from './dbTest';
|
|
3
3
|
import { runCommonKeyValueDBTest } from './keyValueDBTest';
|
|
4
|
-
import { createTestItemBM, createTestItemDBM, createTestItemsBM, createTestItemsDBM,
|
|
4
|
+
import { createTestItemBM, createTestItemDBM, createTestItemsBM, createTestItemsDBM, TestItemBM, testItemBMJsonSchema, testItemBMSchema, TestItemDBM, testItemDBMJsonSchema, testItemDBMSchema, TestItemTM, testItemTMSchema, TEST_TABLE } from './test.model';
|
|
5
5
|
export type { TestItemDBM, TestItemBM, TestItemTM, CommonDBImplementationFeatures, CommonDBImplementationQuirks, };
|
|
6
|
-
export { TEST_TABLE, createTestItemDBM, createTestItemBM, createTestItemsDBM, createTestItemsBM, testItemDBMSchema, testItemBMSchema, testItemTMSchema,
|
|
6
|
+
export { TEST_TABLE, createTestItemDBM, createTestItemBM, createTestItemsDBM, createTestItemsBM, testItemDBMSchema, testItemBMSchema, testItemTMSchema, testItemBMJsonSchema, testItemDBMJsonSchema, runCommonDBTest, runCommonDaoTest, runCommonKeyValueDBTest, };
|
package/dist/testing/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runCommonKeyValueDBTest = exports.runCommonDaoTest = exports.runCommonDBTest = exports.
|
|
3
|
+
exports.runCommonKeyValueDBTest = exports.runCommonDaoTest = exports.runCommonDBTest = exports.testItemDBMJsonSchema = exports.testItemBMJsonSchema = exports.testItemTMSchema = exports.testItemBMSchema = exports.testItemDBMSchema = exports.createTestItemsBM = exports.createTestItemsDBM = exports.createTestItemBM = exports.createTestItemDBM = exports.TEST_TABLE = void 0;
|
|
4
4
|
const daoTest_1 = require("./daoTest");
|
|
5
5
|
Object.defineProperty(exports, "runCommonDaoTest", { enumerable: true, get: function () { return daoTest_1.runCommonDaoTest; } });
|
|
6
6
|
const dbTest_1 = require("./dbTest");
|
|
@@ -12,8 +12,9 @@ Object.defineProperty(exports, "createTestItemBM", { enumerable: true, get: func
|
|
|
12
12
|
Object.defineProperty(exports, "createTestItemDBM", { enumerable: true, get: function () { return test_model_1.createTestItemDBM; } });
|
|
13
13
|
Object.defineProperty(exports, "createTestItemsBM", { enumerable: true, get: function () { return test_model_1.createTestItemsBM; } });
|
|
14
14
|
Object.defineProperty(exports, "createTestItemsDBM", { enumerable: true, get: function () { return test_model_1.createTestItemsDBM; } });
|
|
15
|
-
Object.defineProperty(exports, "
|
|
15
|
+
Object.defineProperty(exports, "testItemBMJsonSchema", { enumerable: true, get: function () { return test_model_1.testItemBMJsonSchema; } });
|
|
16
16
|
Object.defineProperty(exports, "testItemBMSchema", { enumerable: true, get: function () { return test_model_1.testItemBMSchema; } });
|
|
17
|
+
Object.defineProperty(exports, "testItemDBMJsonSchema", { enumerable: true, get: function () { return test_model_1.testItemDBMJsonSchema; } });
|
|
17
18
|
Object.defineProperty(exports, "testItemDBMSchema", { enumerable: true, get: function () { return test_model_1.testItemDBMSchema; } });
|
|
18
19
|
Object.defineProperty(exports, "testItemTMSchema", { enumerable: true, get: function () { return test_model_1.testItemTMSchema; } });
|
|
19
20
|
Object.defineProperty(exports, "TEST_TABLE", { enumerable: true, get: function () { return test_model_1.TEST_TABLE; } });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import {
|
|
2
|
+
import { BaseDBEntity, Saved } from '@naturalcycles/js-lib';
|
|
3
3
|
export declare const TEST_TABLE = "TEST_TABLE";
|
|
4
4
|
export interface TestItemBM extends BaseDBEntity {
|
|
5
5
|
k1: string;
|
|
@@ -23,4 +23,3 @@ export declare function createTestItemDBM(num?: number): TestItemDBM;
|
|
|
23
23
|
export declare function createTestItemBM(num?: number): Saved<TestItemBM>;
|
|
24
24
|
export declare function createTestItemsDBM(count?: number): TestItemDBM[];
|
|
25
25
|
export declare function createTestItemsBM(count?: number): Saved<TestItemBM>[];
|
|
26
|
-
export declare function getTestItemSchema(): JsonSchemaObject<TestItemDBM>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.createTestItemsBM = exports.createTestItemsDBM = exports.createTestItemBM = exports.createTestItemDBM = exports.testItemDBMJsonSchema = exports.testItemBMJsonSchema = exports.testItemTMSchema = exports.testItemDBMSchema = exports.testItemBMSchema = exports.TEST_TABLE = void 0;
|
|
4
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
5
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
6
|
const MOCK_TS_2018_06_21 = 1529539200;
|
|
@@ -67,19 +67,3 @@ function createTestItemsBM(count = 1) {
|
|
|
67
67
|
return (0, js_lib_1._range)(1, count + 1).map(num => createTestItemBM(num));
|
|
68
68
|
}
|
|
69
69
|
exports.createTestItemsBM = createTestItemsBM;
|
|
70
|
-
const testItemJsonSchema = js_lib_1.jsonSchema
|
|
71
|
-
.object({
|
|
72
|
-
id: js_lib_1.jsonSchema.string(),
|
|
73
|
-
k1: js_lib_1.jsonSchema.string(),
|
|
74
|
-
k2: js_lib_1.jsonSchema.string(),
|
|
75
|
-
k3: js_lib_1.jsonSchema.number(),
|
|
76
|
-
even: js_lib_1.jsonSchema.boolean(),
|
|
77
|
-
created: js_lib_1.jsonSchema.unixTimestamp(),
|
|
78
|
-
updated: js_lib_1.jsonSchema.unixTimestamp(),
|
|
79
|
-
})
|
|
80
|
-
.build();
|
|
81
|
-
function getTestItemSchema() {
|
|
82
|
-
// return CommonSchemaGenerator.generateFromRows({ table: TEST_TABLE }, createTestItemsDBM())
|
|
83
|
-
return testItemJsonSchema;
|
|
84
|
-
}
|
|
85
|
-
exports.getTestItemSchema = getTestItemSchema;
|
package/package.json
CHANGED
|
@@ -57,7 +57,7 @@ export interface CacheDBOptions {
|
|
|
57
57
|
onlyCache?: boolean
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export interface CacheDBSaveOptions<ROW extends ObjectWithId
|
|
60
|
+
export interface CacheDBSaveOptions<ROW extends Partial<ObjectWithId>>
|
|
61
61
|
extends CacheDBOptions,
|
|
62
62
|
CommonDBSaveOptions<ROW> {}
|
|
63
63
|
|
|
@@ -146,7 +146,7 @@ export class CacheDB extends BaseCommonDB implements CommonDB {
|
|
|
146
146
|
return deletedIds
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
override async saveBatch<ROW extends ObjectWithId
|
|
149
|
+
override async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
150
150
|
table: string,
|
|
151
151
|
rows: ROW[],
|
|
152
152
|
opt: CacheDBSaveOptions<ROW> = {},
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
_filterUndefinedValues,
|
|
14
14
|
ObjectWithId,
|
|
15
15
|
AnyObjectWithId,
|
|
16
|
+
_assert,
|
|
16
17
|
} from '@naturalcycles/js-lib'
|
|
17
18
|
import { readableCreate, ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
18
19
|
import { dimGrey } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
@@ -71,7 +72,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
71
72
|
return ids.map(id => byId[id]!).filter(Boolean)
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
override async saveBatch<ROW extends ObjectWithId
|
|
75
|
+
override async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
75
76
|
table: string,
|
|
76
77
|
rows: ROW[],
|
|
77
78
|
_opt?: CommonDBSaveOptions<ROW>,
|
|
@@ -79,13 +80,15 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
79
80
|
if (!rows.length) return // save some api calls
|
|
80
81
|
|
|
81
82
|
// 1. Load the whole file
|
|
82
|
-
const byId = _by(await this.loadFile<ROW>(table), r => r.id)
|
|
83
|
+
const byId = _by(await this.loadFile<ROW & ObjectWithId>(table), r => r.id)
|
|
83
84
|
|
|
84
85
|
// 2. Merge with new data (using ids)
|
|
85
86
|
let saved = 0
|
|
86
87
|
rows.forEach(r => {
|
|
88
|
+
_assert(r.id, 'FileDB: row.id is required')
|
|
89
|
+
|
|
87
90
|
if (!_deepEquals(byId[r.id], r)) {
|
|
88
|
-
byId[r.id] = r
|
|
91
|
+
byId[r.id] = r as any
|
|
89
92
|
saved++
|
|
90
93
|
}
|
|
91
94
|
})
|
|
@@ -151,10 +151,10 @@ export class InMemoryDB implements CommonDB {
|
|
|
151
151
|
return ids.map(id => this.data[table]![id]).filter(Boolean) as ROW[]
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
async saveBatch<ROW extends ObjectWithId
|
|
154
|
+
async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
155
155
|
_table: string,
|
|
156
156
|
rows: ROW[],
|
|
157
|
-
|
|
157
|
+
opt: CommonDBSaveOptions<ROW> = {},
|
|
158
158
|
): Promise<void> {
|
|
159
159
|
const table = this.cfg.tablesPrefix + _table
|
|
160
160
|
this.data[table] ||= {}
|
|
@@ -162,20 +162,23 @@ export class InMemoryDB implements CommonDB {
|
|
|
162
162
|
rows.forEach(r => {
|
|
163
163
|
if (!r.id) {
|
|
164
164
|
this.cfg.logger?.warn({ rows })
|
|
165
|
-
throw new Error(
|
|
165
|
+
throw new Error(
|
|
166
|
+
`InMemoryDB doesn't support id auto-generation in saveBatch, row without id was given`,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (opt.saveMethod === 'insert' && this.data[table]![r.id]) {
|
|
171
|
+
throw new Error(`InMemoryDB: INSERT failed, entity exists: ${table}.${r.id}`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (opt.saveMethod === 'update' && !this.data[table]![r.id]) {
|
|
175
|
+
throw new Error(`InMemoryDB: UPDATE failed, entity doesn't exist: ${table}.${r.id}`)
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
// JSON parse/stringify (deep clone) is to:
|
|
169
179
|
// 1. Not store values "by reference" (avoid mutation bugs)
|
|
170
180
|
// 2. Simulate real DB that would do something like that in a transport layer anyway
|
|
171
181
|
this.data[table]![r.id] = JSON.parse(JSON.stringify(r), bufferReviver)
|
|
172
|
-
|
|
173
|
-
// special treatment for Buffers (assign them raw, without JSON parse/stringify)
|
|
174
|
-
// Object.entries(r).forEach(([k, v]) => {
|
|
175
|
-
// if (Buffer.isBuffer(v)) {
|
|
176
|
-
// this.data[table]![r.id]![k] = v
|
|
177
|
-
// }
|
|
178
|
-
// })
|
|
179
182
|
})
|
|
180
183
|
}
|
|
181
184
|
|
package/src/base.common.db.ts
CHANGED
package/src/common.db.ts
CHANGED
|
@@ -70,7 +70,10 @@ export interface CommonDB {
|
|
|
70
70
|
): ReadableTyped<ROW>
|
|
71
71
|
|
|
72
72
|
// SAVE
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment).
|
|
75
|
+
*/
|
|
76
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
74
77
|
table: string,
|
|
75
78
|
rows: ROW[],
|
|
76
79
|
opt?: CommonDBSaveOptions<ROW>,
|
|
@@ -73,18 +73,19 @@ export interface CommonDaoCfg<
|
|
|
73
73
|
excludeFromIndexes?: (keyof DBM)[]
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* * saveBatch, delete*() and patch() will throw.
|
|
76
|
+
* Defaults to false.
|
|
77
|
+
* Setting it to true will set saveMethod to `insert` for save/saveBatch, which will
|
|
78
|
+
* fail for rows that already exist in the DB (if CommonDB implementation supports it).
|
|
80
79
|
*
|
|
81
|
-
*
|
|
80
|
+
* `delete*` and `patch` will throw.
|
|
81
|
+
*
|
|
82
|
+
* You can still override saveMethod, or set opt.allowMutability to allow deletion.
|
|
82
83
|
*/
|
|
83
84
|
immutable?: boolean
|
|
84
85
|
|
|
85
86
|
/**
|
|
86
|
-
*
|
|
87
|
-
* Set to true to limit DB writing (will throw an error
|
|
87
|
+
* Defaults to false.
|
|
88
|
+
* Set to true to limit DB writing (will throw an error in such case).
|
|
88
89
|
*/
|
|
89
90
|
readOnly?: boolean
|
|
90
91
|
|
|
@@ -209,7 +210,7 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
209
210
|
/**
|
|
210
211
|
* All properties default to undefined.
|
|
211
212
|
*/
|
|
212
|
-
export interface CommonDaoSaveOptions<DBM extends ObjectWithId
|
|
213
|
+
export interface CommonDaoSaveOptions<DBM extends Partial<ObjectWithId>>
|
|
213
214
|
extends CommonDaoOptions,
|
|
214
215
|
CommonDBSaveOptions<DBM> {
|
|
215
216
|
/**
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
pMap,
|
|
16
16
|
pTimeout,
|
|
17
17
|
Saved,
|
|
18
|
+
Unsaved,
|
|
18
19
|
} from '@naturalcycles/js-lib'
|
|
19
20
|
import {
|
|
20
21
|
_pipeline,
|
|
@@ -244,8 +245,8 @@ export class CommonDao<
|
|
|
244
245
|
/**
|
|
245
246
|
* Throws if readOnly is true
|
|
246
247
|
*/
|
|
247
|
-
private requireObjectMutability(): void {
|
|
248
|
-
if (this.cfg.immutable) {
|
|
248
|
+
private requireObjectMutability(opt: CommonDaoOptions): void {
|
|
249
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
249
250
|
throw new AppError(DBLibError.OBJECT_IS_IMMUTABLE, {
|
|
250
251
|
code: DBLibError.OBJECT_IS_IMMUTABLE,
|
|
251
252
|
table: this.cfg.table,
|
|
@@ -253,6 +254,18 @@ export class CommonDao<
|
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
private async ensureUniqueId(table: string, dbm: DBM): Promise<void> {
|
|
258
|
+
// todo: retry N times
|
|
259
|
+
const existing = await this.cfg.db.getByIds<DBM>(table, [dbm.id])
|
|
260
|
+
if (existing.length) {
|
|
261
|
+
throw new AppError(DBLibError.NON_UNIQUE_ID, {
|
|
262
|
+
table,
|
|
263
|
+
code: DBLibError.NON_UNIQUE_ID,
|
|
264
|
+
ids: existing.map(i => i.id),
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
256
269
|
async getBy(by: keyof DBM, value: any, limit = 0, opt?: CommonDaoOptions): Promise<Saved<BM>[]> {
|
|
257
270
|
return await this.query().filterEq(by, value).limit(limit).runQuery(opt)
|
|
258
271
|
}
|
|
@@ -573,10 +586,11 @@ export class CommonDao<
|
|
|
573
586
|
*/
|
|
574
587
|
assignIdCreatedUpdated(obj: DBM, opt?: CommonDaoOptions): DBM
|
|
575
588
|
assignIdCreatedUpdated(obj: BM, opt?: CommonDaoOptions): Saved<BM>
|
|
576
|
-
assignIdCreatedUpdated(obj:
|
|
589
|
+
assignIdCreatedUpdated(obj: Unsaved<BM>, opt?: CommonDaoOptions): Saved<BM>
|
|
590
|
+
assignIdCreatedUpdated(obj: DBM | BM | Unsaved<BM>, opt: CommonDaoOptions = {}): DBM | Saved<BM> {
|
|
577
591
|
const now = Math.floor(Date.now() / 1000)
|
|
578
592
|
|
|
579
|
-
obj.id ||= this.cfg.hooks!.createId?.(obj)
|
|
593
|
+
obj.id ||= this.cfg.hooks!.createId?.(obj as BM)
|
|
580
594
|
|
|
581
595
|
if (this.cfg.created) {
|
|
582
596
|
obj['created'] ||= obj['updated'] || now
|
|
@@ -593,14 +607,16 @@ export class CommonDao<
|
|
|
593
607
|
/**
|
|
594
608
|
* Mutates with id, created, updated
|
|
595
609
|
*/
|
|
596
|
-
async save(bm: BM
|
|
610
|
+
async save(bm: Unsaved<BM>, opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>> {
|
|
597
611
|
this.requireWriteAccess()
|
|
598
612
|
const idWasGenerated = !bm.id
|
|
599
613
|
this.assignIdCreatedUpdated(bm, opt) // mutates
|
|
600
|
-
const dbm = await this.bmToDBM(bm, opt)
|
|
614
|
+
const dbm = await this.bmToDBM(bm as BM, opt)
|
|
601
615
|
const table = opt.table || this.cfg.table
|
|
602
616
|
if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
|
|
603
|
-
if (this.cfg.immutable
|
|
617
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
618
|
+
opt.saveMethod ||= 'insert'
|
|
619
|
+
}
|
|
604
620
|
const op = `save(${dbm.id})`
|
|
605
621
|
const started = this.logSaveStarted(op, bm, table)
|
|
606
622
|
await this.cfg.db.saveBatch(table, [dbm], {
|
|
@@ -612,41 +628,6 @@ export class CommonDao<
|
|
|
612
628
|
return bm as any
|
|
613
629
|
}
|
|
614
630
|
|
|
615
|
-
private async ensureImmutableDontExist(table: string, ids: ID[]): Promise<void> {
|
|
616
|
-
await this.throwIfObjectExists(table, ids, [
|
|
617
|
-
DBLibError.OBJECT_IS_IMMUTABLE,
|
|
618
|
-
{
|
|
619
|
-
code: DBLibError.OBJECT_IS_IMMUTABLE,
|
|
620
|
-
table,
|
|
621
|
-
},
|
|
622
|
-
])
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
private async ensureUniqueId(table: string, dbm: DBM): Promise<void> {
|
|
626
|
-
// todo: retry N times
|
|
627
|
-
await this.throwIfObjectExists(
|
|
628
|
-
table,
|
|
629
|
-
[dbm.id],
|
|
630
|
-
[
|
|
631
|
-
DBLibError.OBJECT_IS_IMMUTABLE,
|
|
632
|
-
{
|
|
633
|
-
code: DBLibError.OBJECT_IS_IMMUTABLE,
|
|
634
|
-
table,
|
|
635
|
-
},
|
|
636
|
-
],
|
|
637
|
-
)
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
private async throwIfObjectExists(
|
|
641
|
-
table: string,
|
|
642
|
-
ids: ID[],
|
|
643
|
-
errorMeta: [DBLibError, any],
|
|
644
|
-
): Promise<void> {
|
|
645
|
-
const existing = await this.cfg.db.getByIds<DBM>(table, ids)
|
|
646
|
-
if (existing.length > 0)
|
|
647
|
-
throw new AppError(errorMeta[0], { ...errorMeta[1], ids: existing.map(i => i.id) })
|
|
648
|
-
}
|
|
649
|
-
|
|
650
631
|
/**
|
|
651
632
|
* Loads the row by id.
|
|
652
633
|
* Creates the row (via this.create()) if it doesn't exist
|
|
@@ -690,7 +671,9 @@ export class CommonDao<
|
|
|
690
671
|
this.assignIdCreatedUpdated(dbm, opt) // mutates
|
|
691
672
|
dbm = this.anyToDBM(dbm, opt)
|
|
692
673
|
if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
|
|
693
|
-
|
|
674
|
+
}
|
|
675
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
676
|
+
opt.saveMethod ||= 'insert'
|
|
694
677
|
}
|
|
695
678
|
const op = `saveAsDBM(${dbm.id})`
|
|
696
679
|
const started = this.logSaveStarted(op, dbm, table)
|
|
@@ -702,17 +685,15 @@ export class CommonDao<
|
|
|
702
685
|
return dbm
|
|
703
686
|
}
|
|
704
687
|
|
|
705
|
-
async saveBatch(bms: BM[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>[]> {
|
|
688
|
+
async saveBatch(bms: Unsaved<BM>[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>[]> {
|
|
706
689
|
this.requireWriteAccess()
|
|
707
690
|
const table = opt.table || this.cfg.table
|
|
708
691
|
bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt))
|
|
709
|
-
const dbms = await this.bmsToDBM(bms, opt)
|
|
692
|
+
const dbms = await this.bmsToDBM(bms as BM[], opt)
|
|
710
693
|
if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
|
|
711
|
-
if (this.cfg.immutable)
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
dbms.map(dbm => dbm.id),
|
|
715
|
-
)
|
|
694
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
695
|
+
opt.saveMethod ||= 'insert'
|
|
696
|
+
}
|
|
716
697
|
|
|
717
698
|
const op = `saveBatch ${dbms.length} row(s) (${_truncate(
|
|
718
699
|
dbms
|
|
@@ -740,11 +721,9 @@ export class CommonDao<
|
|
|
740
721
|
dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)) // mutates
|
|
741
722
|
dbms = this.anyToDBMs(dbms, opt)
|
|
742
723
|
if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
dbms.map(dbm => dbm.id),
|
|
747
|
-
)
|
|
724
|
+
}
|
|
725
|
+
if (this.cfg.immutable && !opt.allowMutability) {
|
|
726
|
+
opt.saveMethod ||= 'insert'
|
|
748
727
|
}
|
|
749
728
|
const op = `saveBatchAsDBM ${dbms.length} row(s) (${_truncate(
|
|
750
729
|
dbms
|
|
@@ -773,7 +752,7 @@ export class CommonDao<
|
|
|
773
752
|
async deleteById(id?: ID, opt: CommonDaoOptions = {}): Promise<number> {
|
|
774
753
|
if (!id) return 0
|
|
775
754
|
this.requireWriteAccess()
|
|
776
|
-
|
|
755
|
+
this.requireObjectMutability(opt)
|
|
777
756
|
const op = `deleteById(${id})`
|
|
778
757
|
const table = opt.table || this.cfg.table
|
|
779
758
|
const started = this.logStarted(op, table)
|
|
@@ -784,7 +763,7 @@ export class CommonDao<
|
|
|
784
763
|
|
|
785
764
|
async deleteByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<number> {
|
|
786
765
|
this.requireWriteAccess()
|
|
787
|
-
|
|
766
|
+
this.requireObjectMutability(opt)
|
|
788
767
|
const op = `deleteByIds(${ids.join(', ')})`
|
|
789
768
|
const table = opt.table || this.cfg.table
|
|
790
769
|
const started = this.logStarted(op, table)
|
|
@@ -803,7 +782,7 @@ export class CommonDao<
|
|
|
803
782
|
opt: CommonDaoStreamForEachOptions<DBM> & { stream?: boolean } = {},
|
|
804
783
|
): Promise<number> {
|
|
805
784
|
this.requireWriteAccess()
|
|
806
|
-
|
|
785
|
+
this.requireObjectMutability(opt)
|
|
807
786
|
q.table = opt.table || q.table
|
|
808
787
|
const op = `deleteByQuery(${q.pretty()})`
|
|
809
788
|
const started = this.logStarted(op, q.table)
|
package/src/db.model.ts
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import { AnyObjectWithId, ObjectWithId } from '@naturalcycles/js-lib'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Similar to SQL INSERT, UPDATE.
|
|
5
|
+
* Insert will fail if row already exists.
|
|
6
|
+
* Update will fail if row is missing.
|
|
7
|
+
* Upsert will auto-detect and use Insert or Update to not fail.
|
|
8
|
+
*
|
|
9
|
+
* Default is Upsert.
|
|
10
|
+
*/
|
|
11
|
+
export type CommonDBSaveMethod = 'upsert' | 'insert' | 'update'
|
|
12
|
+
|
|
3
13
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
4
14
|
export interface CommonDBOptions {}
|
|
5
15
|
|
|
6
16
|
/**
|
|
7
17
|
* All properties default to undefined.
|
|
8
18
|
*/
|
|
9
|
-
export interface CommonDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId>
|
|
19
|
+
export interface CommonDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObjectWithId>
|
|
10
20
|
extends CommonDBOptions {
|
|
11
21
|
excludeFromIndexes?: (keyof ROW)[]
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default is `upsert`
|
|
25
|
+
*/
|
|
26
|
+
saveMethod?: CommonDBSaveMethod
|
|
12
27
|
}
|
|
13
28
|
|
|
14
29
|
export type CommonDBStreamOptions = CommonDBOptions
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
CommonDBCreateOptions,
|
|
20
20
|
CommonDBOptions,
|
|
21
|
+
CommonDBSaveMethod,
|
|
21
22
|
CommonDBSaveOptions,
|
|
22
23
|
CommonDBStreamOptions,
|
|
23
24
|
DBDeleteByIdsOperation,
|
|
@@ -62,6 +63,7 @@ export type {
|
|
|
62
63
|
CommonDaoHooks,
|
|
63
64
|
CommonDBOptions,
|
|
64
65
|
CommonDBSaveOptions,
|
|
66
|
+
CommonDBSaveMethod,
|
|
65
67
|
CommonDBStreamOptions,
|
|
66
68
|
CommonDBCreateOptions,
|
|
67
69
|
CommonDB,
|
package/src/testing/daoTest.ts
CHANGED
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
testItemTMSchema,
|
|
12
12
|
TEST_TABLE,
|
|
13
13
|
createTestItemBM,
|
|
14
|
+
testItemDBMJsonSchema,
|
|
14
15
|
} from './test.model'
|
|
15
|
-
import {
|
|
16
|
+
import { TestItemBM } from '.'
|
|
16
17
|
|
|
17
18
|
export function runCommonDaoTest(
|
|
18
19
|
db: CommonDB,
|
|
@@ -65,7 +66,7 @@ export function runCommonDaoTest(
|
|
|
65
66
|
// CREATE TABLE, DROP
|
|
66
67
|
if (createTable) {
|
|
67
68
|
test('createTable, dropIfExists=true', async () => {
|
|
68
|
-
await dao.createTable(
|
|
69
|
+
await dao.createTable(testItemDBMJsonSchema.build(), { dropIfExists: true })
|
|
69
70
|
})
|
|
70
71
|
}
|
|
71
72
|
|
package/src/testing/dbTest.ts
CHANGED
|
@@ -5,9 +5,9 @@ import { DBQuery } from '../query/dbQuery'
|
|
|
5
5
|
import {
|
|
6
6
|
createTestItemDBM,
|
|
7
7
|
createTestItemsDBM,
|
|
8
|
-
getTestItemSchema,
|
|
9
8
|
TestItemDBM,
|
|
10
9
|
TEST_TABLE,
|
|
10
|
+
testItemDBMJsonSchema,
|
|
11
11
|
} from './test.model'
|
|
12
12
|
import { deepFreeze } from './test.util'
|
|
13
13
|
|
|
@@ -21,6 +21,8 @@ export interface CommonDBImplementationFeatures {
|
|
|
21
21
|
dbQueryFilterIn?: boolean
|
|
22
22
|
dbQueryOrder?: boolean
|
|
23
23
|
dbQuerySelectFields?: boolean
|
|
24
|
+
insert?: boolean
|
|
25
|
+
update?: boolean
|
|
24
26
|
|
|
25
27
|
createTable?: boolean
|
|
26
28
|
tableSchemas?: boolean
|
|
@@ -80,6 +82,8 @@ export function runCommonDBTest(
|
|
|
80
82
|
// dbQueryFilterIn = true,
|
|
81
83
|
dbQueryOrder = true,
|
|
82
84
|
dbQuerySelectFields = true,
|
|
85
|
+
insert = true,
|
|
86
|
+
update = true,
|
|
83
87
|
streaming = true,
|
|
84
88
|
strongConsistency = true,
|
|
85
89
|
bufferSupport = true,
|
|
@@ -106,7 +110,7 @@ export function runCommonDBTest(
|
|
|
106
110
|
// CREATE TABLE, DROP
|
|
107
111
|
if (createTable) {
|
|
108
112
|
test('createTable, dropIfExists=true', async () => {
|
|
109
|
-
await db.createTable(TEST_TABLE,
|
|
113
|
+
await db.createTable(TEST_TABLE, testItemDBMJsonSchema.build(), { dropIfExists: true })
|
|
110
114
|
})
|
|
111
115
|
}
|
|
112
116
|
|
|
@@ -176,10 +180,28 @@ export function runCommonDBTest(
|
|
|
176
180
|
})
|
|
177
181
|
}
|
|
178
182
|
|
|
183
|
+
if (update) {
|
|
184
|
+
test('saveBatch UPDATE method should throw', async () => {
|
|
185
|
+
await expect(db.saveBatch(TEST_TABLE, items, { saveMethod: 'update' })).rejects.toThrow()
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
179
189
|
test('saveBatch test items', async () => {
|
|
180
190
|
await db.saveBatch(TEST_TABLE, items)
|
|
181
191
|
})
|
|
182
192
|
|
|
193
|
+
if (insert) {
|
|
194
|
+
test('saveBatch INSERT method should throw', async () => {
|
|
195
|
+
await expect(db.saveBatch(TEST_TABLE, items, { saveMethod: 'insert' })).rejects.toThrow()
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (update) {
|
|
200
|
+
test('saveBatch UPDATE method should pass', async () => {
|
|
201
|
+
await db.saveBatch(TEST_TABLE, items, { saveMethod: 'update' })
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
183
205
|
// GET not empty
|
|
184
206
|
test('getByIds all items', async () => {
|
|
185
207
|
const rows = await db.getByIds<TestItemDBM>(TEST_TABLE, items.map(i => i.id).concat('abcd'))
|
package/src/testing/index.ts
CHANGED
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
createTestItemDBM,
|
|
11
11
|
createTestItemsBM,
|
|
12
12
|
createTestItemsDBM,
|
|
13
|
-
getTestItemSchema,
|
|
14
13
|
TestItemBM,
|
|
14
|
+
testItemBMJsonSchema,
|
|
15
15
|
testItemBMSchema,
|
|
16
16
|
TestItemDBM,
|
|
17
|
+
testItemDBMJsonSchema,
|
|
17
18
|
testItemDBMSchema,
|
|
18
19
|
TestItemTM,
|
|
19
20
|
testItemTMSchema,
|
|
@@ -37,7 +38,8 @@ export {
|
|
|
37
38
|
testItemDBMSchema,
|
|
38
39
|
testItemBMSchema,
|
|
39
40
|
testItemTMSchema,
|
|
40
|
-
|
|
41
|
+
testItemBMJsonSchema,
|
|
42
|
+
testItemDBMJsonSchema,
|
|
41
43
|
runCommonDBTest,
|
|
42
44
|
runCommonDaoTest,
|
|
43
45
|
runCommonKeyValueDBTest,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsonSchema,
|
|
1
|
+
import { jsonSchema, _range, BaseDBEntity, Saved } from '@naturalcycles/js-lib'
|
|
2
2
|
import {
|
|
3
3
|
baseDBEntitySchema,
|
|
4
4
|
binarySchema,
|
|
@@ -94,20 +94,3 @@ export function createTestItemsDBM(count = 1): TestItemDBM[] {
|
|
|
94
94
|
export function createTestItemsBM(count = 1): Saved<TestItemBM>[] {
|
|
95
95
|
return _range(1, count + 1).map(num => createTestItemBM(num))
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
const testItemJsonSchema = jsonSchema
|
|
99
|
-
.object<TestItemDBM>({
|
|
100
|
-
id: jsonSchema.string(),
|
|
101
|
-
k1: jsonSchema.string(),
|
|
102
|
-
k2: jsonSchema.string(),
|
|
103
|
-
k3: jsonSchema.number(),
|
|
104
|
-
even: jsonSchema.boolean(),
|
|
105
|
-
created: jsonSchema.unixTimestamp(),
|
|
106
|
-
updated: jsonSchema.unixTimestamp(),
|
|
107
|
-
})
|
|
108
|
-
.build()
|
|
109
|
-
|
|
110
|
-
export function getTestItemSchema(): JsonSchemaObject<TestItemDBM> {
|
|
111
|
-
// return CommonSchemaGenerator.generateFromRows({ table: TEST_TABLE }, createTestItemsDBM())
|
|
112
|
-
return testItemJsonSchema
|
|
113
|
-
}
|