@naturalcycles/db-lib 8.31.0 → 8.34.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/cnst.d.ts +2 -1
- package/dist/cnst.js +1 -0
- package/dist/commondao/common.dao.d.ts +6 -3
- package/dist/commondao/common.dao.js +46 -9
- package/dist/commondao/common.dao.model.d.ts +13 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/kv/commonKeyValueDao.d.ts +7 -0
- package/dist/kv/commonKeyValueDao.js +20 -0
- package/dist/kv/commonKeyValueDaoMemoCache.d.ts +17 -0
- package/dist/kv/commonKeyValueDaoMemoCache.js +30 -0
- package/package.json +1 -1
- package/src/cnst.ts +1 -0
- package/src/commondao/common.dao.model.ts +15 -0
- package/src/commondao/common.dao.ts +46 -9
- package/src/index.ts +1 -0
- package/src/kv/commonKeyValueDao.ts +39 -3
- package/src/kv/commonKeyValueDaoMemoCache.ts +33 -0
package/dist/cnst.d.ts
CHANGED
package/dist/cnst.js
CHANGED
|
@@ -6,4 +6,5 @@ var DBLibError;
|
|
|
6
6
|
DBLibError["DB_ROW_REQUIRED"] = "DB_ROW_REQUIRED";
|
|
7
7
|
DBLibError["DAO_IS_READ_ONLY"] = "DAO_IS_READ_ONLY";
|
|
8
8
|
DBLibError["NON_UNIQUE_ID"] = "NON_UNIQUE_ID";
|
|
9
|
+
DBLibError["OBJECT_IS_IMMUTABLE"] = "OBJECT_IS_IMMUTABLE";
|
|
9
10
|
})(DBLibError = exports.DBLibError || (exports.DBLibError = {}));
|
|
@@ -31,6 +31,10 @@ export declare class CommonDao<BM extends Partial<ObjectWithId>, DBM extends Obj
|
|
|
31
31
|
* Throws if readOnly is true
|
|
32
32
|
*/
|
|
33
33
|
private requireWriteAccess;
|
|
34
|
+
/**
|
|
35
|
+
* Throws if readOnly is true
|
|
36
|
+
*/
|
|
37
|
+
private requireObjectMutability;
|
|
34
38
|
getBy(by: keyof DBM, value: any, limit?: number, opt?: CommonDaoOptions): Promise<Saved<BM>[]>;
|
|
35
39
|
getOneBy(by: keyof DBM, value: any, opt?: CommonDaoOptions): Promise<Saved<BM> | null>;
|
|
36
40
|
getAll(opt?: CommonDaoOptions): Promise<Saved<BM>[]>;
|
|
@@ -81,10 +85,9 @@ export declare class CommonDao<BM extends Partial<ObjectWithId>, DBM extends Obj
|
|
|
81
85
|
* Mutates with id, created, updated
|
|
82
86
|
*/
|
|
83
87
|
save(bm: BM, opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>>;
|
|
84
|
-
|
|
85
|
-
* Mutates id if needed
|
|
86
|
-
*/
|
|
88
|
+
private ensureImmutableDoesntExist;
|
|
87
89
|
private ensureUniqueId;
|
|
90
|
+
private throwIfObjectExists;
|
|
88
91
|
/**
|
|
89
92
|
* Loads the row by id.
|
|
90
93
|
* Creates the row (via this.create()) if it doesn't exist
|
|
@@ -164,6 +164,17 @@ class CommonDao {
|
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Throws if readOnly is true
|
|
169
|
+
*/
|
|
170
|
+
requireObjectMutability() {
|
|
171
|
+
if (this.cfg.immutable) {
|
|
172
|
+
throw new js_lib_1.AppError(cnst_1.DBLibError.OBJECT_IS_IMMUTABLE, {
|
|
173
|
+
code: cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
174
|
+
table: this.cfg.table,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
167
178
|
async getBy(by, value, limit = 0, opt) {
|
|
168
179
|
return await this.query().filterEq(by, value).limit(limit).runQuery(opt);
|
|
169
180
|
}
|
|
@@ -430,6 +441,8 @@ class CommonDao {
|
|
|
430
441
|
const table = opt.table || this.cfg.table;
|
|
431
442
|
if (opt.ensureUniqueId && idWasGenerated)
|
|
432
443
|
await this.ensureUniqueId(table, dbm);
|
|
444
|
+
if (this.cfg.immutable)
|
|
445
|
+
await this.ensureImmutableDoesntExist(table, dbm);
|
|
433
446
|
const op = `save(${dbm.id})`;
|
|
434
447
|
const started = this.logSaveStarted(op, bm, table);
|
|
435
448
|
await this.cfg.db.saveBatch(table, [dbm], {
|
|
@@ -439,19 +452,31 @@ class CommonDao {
|
|
|
439
452
|
this.logSaveResult(started, op, table);
|
|
440
453
|
return bm;
|
|
441
454
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
455
|
+
async ensureImmutableDoesntExist(table, dbm) {
|
|
456
|
+
await this.throwIfObjectExists(table, dbm, [
|
|
457
|
+
cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
458
|
+
{
|
|
459
|
+
code: cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
460
|
+
id: dbm.id,
|
|
461
|
+
table,
|
|
462
|
+
},
|
|
463
|
+
]);
|
|
464
|
+
}
|
|
445
465
|
async ensureUniqueId(table, dbm) {
|
|
446
466
|
// todo: retry N times
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
code: cnst_1.DBLibError.
|
|
467
|
+
await this.throwIfObjectExists(table, dbm, [
|
|
468
|
+
cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
469
|
+
{
|
|
470
|
+
code: cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
|
|
451
471
|
id: dbm.id,
|
|
452
472
|
table,
|
|
453
|
-
}
|
|
454
|
-
|
|
473
|
+
},
|
|
474
|
+
]);
|
|
475
|
+
}
|
|
476
|
+
async throwIfObjectExists(table, dbm, errorMeta) {
|
|
477
|
+
const [existing] = await this.cfg.db.getByIds(table, [dbm.id]);
|
|
478
|
+
if (existing)
|
|
479
|
+
throw new js_lib_1.AppError(errorMeta[0], errorMeta[1]);
|
|
455
480
|
}
|
|
456
481
|
/**
|
|
457
482
|
* Loads the row by id.
|
|
@@ -486,6 +511,8 @@ class CommonDao {
|
|
|
486
511
|
dbm = this.anyToDBM(dbm, opt);
|
|
487
512
|
if (opt.ensureUniqueId && idWasGenerated)
|
|
488
513
|
await this.ensureUniqueId(table, dbm);
|
|
514
|
+
if (this.cfg.immutable)
|
|
515
|
+
await this.ensureImmutableDoesntExist(table, dbm);
|
|
489
516
|
}
|
|
490
517
|
const op = `saveAsDBM(${dbm.id})`;
|
|
491
518
|
const started = this.logSaveStarted(op, dbm, table);
|
|
@@ -503,6 +530,8 @@ class CommonDao {
|
|
|
503
530
|
const dbms = await this.bmsToDBM(bms, opt);
|
|
504
531
|
if (opt.ensureUniqueId)
|
|
505
532
|
throw new js_lib_1.AppError('ensureUniqueId is not supported in saveBatch');
|
|
533
|
+
if (this.cfg.immutable)
|
|
534
|
+
throw new js_lib_1.AppError('immutable DB entries are not supported in saveBatch');
|
|
506
535
|
const op = `saveBatch ${dbms.length} row(s) (${(0, js_lib_1._truncate)(dbms
|
|
507
536
|
.slice(0, 10)
|
|
508
537
|
.map(bm => bm.id)
|
|
@@ -523,6 +552,8 @@ class CommonDao {
|
|
|
523
552
|
dbms = this.anyToDBMs(dbms, opt);
|
|
524
553
|
if (opt.ensureUniqueId)
|
|
525
554
|
throw new js_lib_1.AppError('ensureUniqueId is not supported in saveBatch');
|
|
555
|
+
if (this.cfg.immutable)
|
|
556
|
+
throw new js_lib_1.AppError('immutable objects are not supported in saveBatch');
|
|
526
557
|
}
|
|
527
558
|
const op = `saveBatchAsDBM ${dbms.length} row(s) (${(0, js_lib_1._truncate)(dbms
|
|
528
559
|
.slice(0, 10)
|
|
@@ -540,6 +571,8 @@ class CommonDao {
|
|
|
540
571
|
if (!id)
|
|
541
572
|
return 0;
|
|
542
573
|
this.requireWriteAccess();
|
|
574
|
+
if (!opt.allowMutabiliity)
|
|
575
|
+
this.requireObjectMutability();
|
|
543
576
|
const op = `deleteById(${id})`;
|
|
544
577
|
const table = opt.table || this.cfg.table;
|
|
545
578
|
const started = this.logStarted(op, table);
|
|
@@ -549,6 +582,8 @@ class CommonDao {
|
|
|
549
582
|
}
|
|
550
583
|
async deleteByIds(ids, opt = {}) {
|
|
551
584
|
this.requireWriteAccess();
|
|
585
|
+
if (!opt.allowMutabiliity)
|
|
586
|
+
this.requireObjectMutability();
|
|
552
587
|
const op = `deleteByIds(${ids.join(', ')})`;
|
|
553
588
|
const table = opt.table || this.cfg.table;
|
|
554
589
|
const started = this.logStarted(op, table);
|
|
@@ -563,6 +598,8 @@ class CommonDao {
|
|
|
563
598
|
*/
|
|
564
599
|
async deleteByQuery(q, opt = {}) {
|
|
565
600
|
this.requireWriteAccess();
|
|
601
|
+
if (!opt.allowMutabiliity)
|
|
602
|
+
this.requireObjectMutability();
|
|
566
603
|
q.table = opt.table || q.table;
|
|
567
604
|
const op = `deleteByQuery(${q.pretty()})`;
|
|
568
605
|
const started = this.logStarted(op, q.table);
|
|
@@ -57,6 +57,15 @@ export interface CommonDaoCfg<BM extends Partial<ObjectWithId>, DBM extends Obje
|
|
|
57
57
|
bmSchema?: ObjectSchemaTyped<BM> | AjvSchema<BM>;
|
|
58
58
|
tmSchema?: ObjectSchemaTyped<TM> | AjvSchema<TM>;
|
|
59
59
|
excludeFromIndexes?: (keyof DBM)[];
|
|
60
|
+
/**
|
|
61
|
+
* @default to false
|
|
62
|
+
* Set to true to limit DB writing:
|
|
63
|
+
* * Will throw an error if an object with matching ID already exists during save().
|
|
64
|
+
* * saveBatch, delete*() and patch() will throw.
|
|
65
|
+
*
|
|
66
|
+
* Although deletion is possible by passing (opt.overrideImmutability === true)
|
|
67
|
+
*/
|
|
68
|
+
immutable?: boolean;
|
|
60
69
|
/**
|
|
61
70
|
* @default to false
|
|
62
71
|
* Set to true to limit DB writing (will throw an error is such case).
|
|
@@ -128,6 +137,10 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
128
137
|
* @default false
|
|
129
138
|
*/
|
|
130
139
|
preserveUpdatedCreated?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* @default false (for streams). Setting to true enables deletion of immutable objects
|
|
142
|
+
*/
|
|
143
|
+
allowMutabiliity?: boolean;
|
|
131
144
|
/**
|
|
132
145
|
* If true - data will be anonymized (by calling a BaseDao.anonymize() hook that you can extend in your Dao implementation).
|
|
133
146
|
* Only applicable to loading/querying/streaming_loading operations (n/a for saving).
|
package/dist/index.d.ts
CHANGED
|
@@ -17,5 +17,6 @@ import { dbPipelineRestore, DBPipelineRestoreOptions } from './pipeline/dbPipeli
|
|
|
17
17
|
import { DBQuery, DBQueryFilter, DBQueryFilterOperator, dbQueryFilterOperatorValues, DBQueryOrder, RunnableDBQuery } from './query/dbQuery';
|
|
18
18
|
import { DBTransaction, RunnableDBTransaction } from './transaction/dbTransaction';
|
|
19
19
|
import { commitDBTransactionSimple, mergeDBOperations } from './transaction/dbTransaction.util';
|
|
20
|
+
export * from './kv/commonKeyValueDaoMemoCache';
|
|
20
21
|
export type { DBQueryFilterOperator, DBQueryFilter, DBQueryOrder, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBCreateOptions, CommonDB, RunQueryResult, CommonDaoCfg, CommonDaoCreateIdHook, CommonDaoParseNaturalIdHook, CommonDaoBeforeCreateHook, CommonDaoBeforeDBMValidateHook, CommonDaoBeforeDBMToBMHook, CommonDaoBeforeBMToDBMHook, CommonDaoBeforeTMToBMHook, CommonDaoBeforeBMToTMHook, CommonDaoAnonymizeHook, InMemoryDBCfg, InMemoryKeyValueDBCfg, DBPipelineBackupOptions, DBPipelineRestoreOptions, DBPipelineCopyOptions, CommonDBAdapter, DBOperation, DBSaveBatchOperation, DBDeleteByIdsOperation, CommonKeyValueDB, CommonKeyValueDaoCfg, KeyValueDBTuple, };
|
|
21
22
|
export { DBQuery, dbQueryFilterOperatorValues, RunnableDBQuery, CommonDaoLogLevel, DBRelation, DBModelType, CommonDao, createdUpdatedFields, createdUpdatedIdFields, InMemoryDB, InMemoryKeyValueDB, queryInMemory, serializeJsonField, deserializeJsonField, dbPipelineBackup, dbPipelineRestore, dbPipelineCopy, getDB, DBLibError, BaseCommonDB, DBTransaction, RunnableDBTransaction, mergeDBOperations, commitDBTransactionSimple, CommonKeyValueDao, };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CommonKeyValueDao = exports.commitDBTransactionSimple = exports.mergeDBOperations = exports.RunnableDBTransaction = exports.DBTransaction = exports.BaseCommonDB = exports.DBLibError = exports.getDB = exports.dbPipelineCopy = exports.dbPipelineRestore = exports.dbPipelineBackup = exports.deserializeJsonField = exports.serializeJsonField = exports.queryInMemory = exports.InMemoryKeyValueDB = exports.InMemoryDB = exports.createdUpdatedIdFields = exports.createdUpdatedFields = exports.CommonDao = exports.DBModelType = exports.DBRelation = exports.CommonDaoLogLevel = exports.RunnableDBQuery = exports.dbQueryFilterOperatorValues = exports.DBQuery = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
4
5
|
const inMemory_db_1 = require("./adapter/inmemory/inMemory.db");
|
|
5
6
|
Object.defineProperty(exports, "InMemoryDB", { enumerable: true, get: function () { return inMemory_db_1.InMemoryDB; } });
|
|
6
7
|
const inMemoryKeyValueDB_1 = require("./adapter/inmemory/inMemoryKeyValueDB");
|
|
@@ -43,3 +44,4 @@ Object.defineProperty(exports, "RunnableDBTransaction", { enumerable: true, get:
|
|
|
43
44
|
const dbTransaction_util_1 = require("./transaction/dbTransaction.util");
|
|
44
45
|
Object.defineProperty(exports, "commitDBTransactionSimple", { enumerable: true, get: function () { return dbTransaction_util_1.commitDBTransactionSimple; } });
|
|
45
46
|
Object.defineProperty(exports, "mergeDBOperations", { enumerable: true, get: function () { return dbTransaction_util_1.mergeDBOperations; } });
|
|
47
|
+
(0, tslib_1.__exportStar)(require("./kv/commonKeyValueDaoMemoCache"), exports);
|
|
@@ -25,6 +25,12 @@ export interface CommonKeyValueDaoCfg<T> {
|
|
|
25
25
|
mapBufferToValue?: (b: Buffer) => Promise<T>;
|
|
26
26
|
beforeCreate?: (v: Partial<T>) => Partial<T>;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Set to `true` to conveniently enable zipping+JSON.stringify
|
|
30
|
+
* (and unzipping+JSON.parse) of the Buffer value via hooks.
|
|
31
|
+
* Custom hooks will override these hooks (if provided).
|
|
32
|
+
*/
|
|
33
|
+
deflatedJsonValue?: boolean;
|
|
28
34
|
}
|
|
29
35
|
export declare class CommonKeyValueDao<T> {
|
|
30
36
|
cfg: CommonKeyValueDaoCfg<T>;
|
|
@@ -33,6 +39,7 @@ export declare class CommonKeyValueDao<T> {
|
|
|
33
39
|
createTable(opt?: CommonDBCreateOptions): Promise<void>;
|
|
34
40
|
create(input?: Partial<T>): T;
|
|
35
41
|
getById(id?: string): Promise<T | null>;
|
|
42
|
+
requireById(id: string): Promise<T>;
|
|
36
43
|
getByIdOrEmpty(id: string, part?: Partial<T>): Promise<T>;
|
|
37
44
|
patch(id: string, patch: Partial<T>): Promise<T>;
|
|
38
45
|
getByIds(ids: string[]): Promise<KeyValueTuple<string, T>[]>;
|
|
@@ -3,11 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CommonKeyValueDao = void 0;
|
|
4
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
5
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
|
+
const cnst_1 = require("../cnst");
|
|
6
7
|
// todo: logging
|
|
7
8
|
// todo: readonly
|
|
8
9
|
class CommonKeyValueDao {
|
|
9
10
|
constructor(cfg) {
|
|
10
11
|
this.cfg = cfg;
|
|
12
|
+
if (cfg.deflatedJsonValue) {
|
|
13
|
+
cfg.hooks = {
|
|
14
|
+
mapValueToBuffer: async (v) => await (0, nodejs_lib_1.deflateString)(JSON.stringify(v)),
|
|
15
|
+
mapBufferToValue: async (buf) => JSON.parse(await (0, nodejs_lib_1.inflateToString)(buf)),
|
|
16
|
+
...cfg.hooks,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
11
19
|
}
|
|
12
20
|
async ping() {
|
|
13
21
|
await this.cfg.db.ping();
|
|
@@ -26,6 +34,18 @@ class CommonKeyValueDao {
|
|
|
26
34
|
const [r] = await this.getByIds([id]);
|
|
27
35
|
return r?.[1] || null;
|
|
28
36
|
}
|
|
37
|
+
async requireById(id) {
|
|
38
|
+
const [r] = await this.getByIds([id]);
|
|
39
|
+
if (!r) {
|
|
40
|
+
const { table } = this.cfg;
|
|
41
|
+
throw new js_lib_1.AppError(`DB row required, but not found: ${table}.${id}`, {
|
|
42
|
+
code: cnst_1.DBLibError.DB_ROW_REQUIRED,
|
|
43
|
+
table,
|
|
44
|
+
id,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return r[1];
|
|
48
|
+
}
|
|
29
49
|
async getByIdOrEmpty(id, part = {}) {
|
|
30
50
|
const [r] = await this.getByIds([id]);
|
|
31
51
|
if (r)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AsyncMemoCache } from '@naturalcycles/js-lib';
|
|
2
|
+
import { CommonKeyValueDao } from './commonKeyValueDao';
|
|
3
|
+
/**
|
|
4
|
+
* AsyncMemoCache implementation, backed by CommonKeyValueDao.
|
|
5
|
+
*
|
|
6
|
+
* Does NOT support persisting Errors, skips them instead.
|
|
7
|
+
*
|
|
8
|
+
* Also, does not support .clear(), as it's more dangerous than useful to actually
|
|
9
|
+
* clear the whole table/cache.
|
|
10
|
+
*/
|
|
11
|
+
export declare class CommonKeyValueDaoMemoCache<VALUE = any> implements AsyncMemoCache<string, VALUE> {
|
|
12
|
+
private dao;
|
|
13
|
+
constructor(dao: CommonKeyValueDao<VALUE>);
|
|
14
|
+
get(k: string): Promise<VALUE | Error | undefined>;
|
|
15
|
+
set(k: string, v: VALUE | Error): Promise<void>;
|
|
16
|
+
clear(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommonKeyValueDaoMemoCache = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* AsyncMemoCache implementation, backed by CommonKeyValueDao.
|
|
6
|
+
*
|
|
7
|
+
* Does NOT support persisting Errors, skips them instead.
|
|
8
|
+
*
|
|
9
|
+
* Also, does not support .clear(), as it's more dangerous than useful to actually
|
|
10
|
+
* clear the whole table/cache.
|
|
11
|
+
*/
|
|
12
|
+
class CommonKeyValueDaoMemoCache {
|
|
13
|
+
constructor(dao) {
|
|
14
|
+
this.dao = dao;
|
|
15
|
+
}
|
|
16
|
+
async get(k) {
|
|
17
|
+
return (await this.dao.getById(k)) || undefined;
|
|
18
|
+
}
|
|
19
|
+
async set(k, v) {
|
|
20
|
+
if (v instanceof Error) {
|
|
21
|
+
// We currently don't persist errors there
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
await this.dao.save(k, v);
|
|
25
|
+
}
|
|
26
|
+
async clear() {
|
|
27
|
+
throw new Error('CommonKeyValueDaoMemoCache.clear is not supported, because cache is expected to be persistent');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.CommonKeyValueDaoMemoCache = CommonKeyValueDaoMemoCache;
|
package/package.json
CHANGED
package/src/cnst.ts
CHANGED
|
@@ -77,6 +77,16 @@ export interface CommonDaoCfg<
|
|
|
77
77
|
|
|
78
78
|
excludeFromIndexes?: (keyof DBM)[]
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @default to false
|
|
82
|
+
* Set to true to limit DB writing:
|
|
83
|
+
* * Will throw an error if an object with matching ID already exists during save().
|
|
84
|
+
* * saveBatch, delete*() and patch() will throw.
|
|
85
|
+
*
|
|
86
|
+
* Although deletion is possible by passing (opt.overrideImmutability === true)
|
|
87
|
+
*/
|
|
88
|
+
immutable?: boolean
|
|
89
|
+
|
|
80
90
|
/**
|
|
81
91
|
* @default to false
|
|
82
92
|
* Set to true to limit DB writing (will throw an error is such case).
|
|
@@ -161,6 +171,11 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
161
171
|
*/
|
|
162
172
|
preserveUpdatedCreated?: boolean
|
|
163
173
|
|
|
174
|
+
/**
|
|
175
|
+
* @default false (for streams). Setting to true enables deletion of immutable objects
|
|
176
|
+
*/
|
|
177
|
+
allowMutabiliity?: boolean
|
|
178
|
+
|
|
164
179
|
/**
|
|
165
180
|
* If true - data will be anonymized (by calling a BaseDao.anonymize() hook that you can extend in your Dao implementation).
|
|
166
181
|
* Only applicable to loading/querying/streaming_loading operations (n/a for saving).
|
|
@@ -238,6 +238,18 @@ export class CommonDao<
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Throws if readOnly is true
|
|
243
|
+
*/
|
|
244
|
+
private requireObjectMutability(): void {
|
|
245
|
+
if (this.cfg.immutable) {
|
|
246
|
+
throw new AppError(DBLibError.OBJECT_IS_IMMUTABLE, {
|
|
247
|
+
code: DBLibError.OBJECT_IS_IMMUTABLE,
|
|
248
|
+
table: this.cfg.table,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
241
253
|
async getBy(by: keyof DBM, value: any, limit = 0, opt?: CommonDaoOptions): Promise<Saved<BM>[]> {
|
|
242
254
|
return await this.query().filterEq(by, value).limit(limit).runQuery(opt)
|
|
243
255
|
}
|
|
@@ -589,6 +601,7 @@ export class CommonDao<
|
|
|
589
601
|
const dbm = await this.bmToDBM(bm, opt)
|
|
590
602
|
const table = opt.table || this.cfg.table
|
|
591
603
|
if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
|
|
604
|
+
if (this.cfg.immutable) await this.ensureImmutableDoesntExist(table, dbm)
|
|
592
605
|
const op = `save(${dbm.id})`
|
|
593
606
|
const started = this.logSaveStarted(op, bm, table)
|
|
594
607
|
await this.cfg.db.saveBatch(table, [dbm], {
|
|
@@ -600,19 +613,36 @@ export class CommonDao<
|
|
|
600
613
|
return bm as any
|
|
601
614
|
}
|
|
602
615
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
616
|
+
private async ensureImmutableDoesntExist(table: string, dbm: DBM): Promise<void> {
|
|
617
|
+
await this.throwIfObjectExists(table, dbm, [
|
|
618
|
+
DBLibError.OBJECT_IS_IMMUTABLE,
|
|
619
|
+
{
|
|
620
|
+
code: DBLibError.OBJECT_IS_IMMUTABLE,
|
|
621
|
+
id: dbm.id,
|
|
622
|
+
table,
|
|
623
|
+
},
|
|
624
|
+
])
|
|
625
|
+
}
|
|
626
|
+
|
|
606
627
|
private async ensureUniqueId(table: string, dbm: DBM): Promise<void> {
|
|
607
628
|
// todo: retry N times
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
code: DBLibError.
|
|
629
|
+
await this.throwIfObjectExists(table, dbm, [
|
|
630
|
+
DBLibError.OBJECT_IS_IMMUTABLE,
|
|
631
|
+
{
|
|
632
|
+
code: DBLibError.OBJECT_IS_IMMUTABLE,
|
|
612
633
|
id: dbm.id,
|
|
613
634
|
table,
|
|
614
|
-
}
|
|
615
|
-
|
|
635
|
+
},
|
|
636
|
+
])
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
private async throwIfObjectExists(
|
|
640
|
+
table: string,
|
|
641
|
+
dbm: DBM,
|
|
642
|
+
errorMeta: [DBLibError, any],
|
|
643
|
+
): Promise<void> {
|
|
644
|
+
const [existing] = await this.cfg.db.getByIds<DBM>(table, [dbm.id])
|
|
645
|
+
if (existing) throw new AppError(errorMeta[0], errorMeta[1])
|
|
616
646
|
}
|
|
617
647
|
|
|
618
648
|
/**
|
|
@@ -666,6 +696,7 @@ export class CommonDao<
|
|
|
666
696
|
this.assignIdCreatedUpdated(dbm, opt) // mutates
|
|
667
697
|
dbm = this.anyToDBM(dbm, opt)
|
|
668
698
|
if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
|
|
699
|
+
if (this.cfg.immutable) await this.ensureImmutableDoesntExist(table, dbm)
|
|
669
700
|
}
|
|
670
701
|
const op = `saveAsDBM(${dbm.id})`
|
|
671
702
|
const started = this.logSaveStarted(op, dbm, table)
|
|
@@ -683,6 +714,8 @@ export class CommonDao<
|
|
|
683
714
|
bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt))
|
|
684
715
|
const dbms = await this.bmsToDBM(bms, opt)
|
|
685
716
|
if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
|
|
717
|
+
if (this.cfg.immutable)
|
|
718
|
+
throw new AppError('immutable DB entries are not supported in saveBatch')
|
|
686
719
|
const op = `saveBatch ${dbms.length} row(s) (${_truncate(
|
|
687
720
|
dbms
|
|
688
721
|
.slice(0, 10)
|
|
@@ -709,6 +742,7 @@ export class CommonDao<
|
|
|
709
742
|
dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)) // mutates
|
|
710
743
|
dbms = this.anyToDBMs(dbms, opt)
|
|
711
744
|
if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
|
|
745
|
+
if (this.cfg.immutable) throw new AppError('immutable objects are not supported in saveBatch')
|
|
712
746
|
}
|
|
713
747
|
const op = `saveBatchAsDBM ${dbms.length} row(s) (${_truncate(
|
|
714
748
|
dbms
|
|
@@ -737,6 +771,7 @@ export class CommonDao<
|
|
|
737
771
|
async deleteById(id?: string, opt: CommonDaoOptions = {}): Promise<number> {
|
|
738
772
|
if (!id) return 0
|
|
739
773
|
this.requireWriteAccess()
|
|
774
|
+
if (!opt.allowMutabiliity) this.requireObjectMutability()
|
|
740
775
|
const op = `deleteById(${id})`
|
|
741
776
|
const table = opt.table || this.cfg.table
|
|
742
777
|
const started = this.logStarted(op, table)
|
|
@@ -747,6 +782,7 @@ export class CommonDao<
|
|
|
747
782
|
|
|
748
783
|
async deleteByIds(ids: string[], opt: CommonDaoOptions = {}): Promise<number> {
|
|
749
784
|
this.requireWriteAccess()
|
|
785
|
+
if (!opt.allowMutabiliity) this.requireObjectMutability()
|
|
750
786
|
const op = `deleteByIds(${ids.join(', ')})`
|
|
751
787
|
const table = opt.table || this.cfg.table
|
|
752
788
|
const started = this.logStarted(op, table)
|
|
@@ -765,6 +801,7 @@ export class CommonDao<
|
|
|
765
801
|
opt: CommonDaoStreamForEachOptions<DBM> & { stream?: boolean } = {},
|
|
766
802
|
): Promise<number> {
|
|
767
803
|
this.requireWriteAccess()
|
|
804
|
+
if (!opt.allowMutabiliity) this.requireObjectMutability()
|
|
768
805
|
q.table = opt.table || q.table
|
|
769
806
|
const op = `deleteByQuery(${q.pretty()})`
|
|
770
807
|
const started = this.logStarted(op, q.table)
|
package/src/index.ts
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
} from './query/dbQuery'
|
|
59
59
|
import { DBTransaction, RunnableDBTransaction } from './transaction/dbTransaction'
|
|
60
60
|
import { commitDBTransactionSimple, mergeDBOperations } from './transaction/dbTransaction.util'
|
|
61
|
+
export * from './kv/commonKeyValueDaoMemoCache'
|
|
61
62
|
|
|
62
63
|
export type {
|
|
63
64
|
DBQueryFilterOperator,
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { ErrorMode, KeyValueTuple, pMap } from '@naturalcycles/js-lib'
|
|
2
|
-
import {
|
|
1
|
+
import { AppError, ErrorMode, KeyValueTuple, pMap } from '@naturalcycles/js-lib'
|
|
2
|
+
import {
|
|
3
|
+
deflateString,
|
|
4
|
+
inflateToString,
|
|
5
|
+
ReadableTyped,
|
|
6
|
+
transformMap,
|
|
7
|
+
} from '@naturalcycles/nodejs-lib'
|
|
8
|
+
import { DBLibError } from '../cnst'
|
|
3
9
|
import { CommonDaoLogLevel } from '../commondao/common.dao.model'
|
|
4
10
|
import { CommonDBCreateOptions } from '../db.model'
|
|
5
11
|
import { CommonKeyValueDB, KeyValueDBTuple } from './commonKeyValueDB'
|
|
@@ -30,13 +36,28 @@ export interface CommonKeyValueDaoCfg<T> {
|
|
|
30
36
|
mapBufferToValue?: (b: Buffer) => Promise<T>
|
|
31
37
|
beforeCreate?: (v: Partial<T>) => Partial<T>
|
|
32
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Set to `true` to conveniently enable zipping+JSON.stringify
|
|
42
|
+
* (and unzipping+JSON.parse) of the Buffer value via hooks.
|
|
43
|
+
* Custom hooks will override these hooks (if provided).
|
|
44
|
+
*/
|
|
45
|
+
deflatedJsonValue?: boolean
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
// todo: logging
|
|
36
49
|
// todo: readonly
|
|
37
50
|
|
|
38
51
|
export class CommonKeyValueDao<T> {
|
|
39
|
-
constructor(public cfg: CommonKeyValueDaoCfg<T>) {
|
|
52
|
+
constructor(public cfg: CommonKeyValueDaoCfg<T>) {
|
|
53
|
+
if (cfg.deflatedJsonValue) {
|
|
54
|
+
cfg.hooks = {
|
|
55
|
+
mapValueToBuffer: async v => await deflateString(JSON.stringify(v)),
|
|
56
|
+
mapBufferToValue: async buf => JSON.parse(await inflateToString(buf)),
|
|
57
|
+
...cfg.hooks,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
40
61
|
|
|
41
62
|
async ping(): Promise<void> {
|
|
42
63
|
await this.cfg.db.ping()
|
|
@@ -58,6 +79,21 @@ export class CommonKeyValueDao<T> {
|
|
|
58
79
|
return r?.[1] || null
|
|
59
80
|
}
|
|
60
81
|
|
|
82
|
+
async requireById(id: string): Promise<T> {
|
|
83
|
+
const [r] = await this.getByIds([id])
|
|
84
|
+
|
|
85
|
+
if (!r) {
|
|
86
|
+
const { table } = this.cfg
|
|
87
|
+
throw new AppError(`DB row required, but not found: ${table}.${id}`, {
|
|
88
|
+
code: DBLibError.DB_ROW_REQUIRED,
|
|
89
|
+
table,
|
|
90
|
+
id,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return r[1]
|
|
95
|
+
}
|
|
96
|
+
|
|
61
97
|
async getByIdOrEmpty(id: string, part: Partial<T> = {}): Promise<T> {
|
|
62
98
|
const [r] = await this.getByIds([id])
|
|
63
99
|
if (r) return r[1]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AsyncMemoCache } from '@naturalcycles/js-lib'
|
|
2
|
+
import { CommonKeyValueDao } from './commonKeyValueDao'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AsyncMemoCache implementation, backed by CommonKeyValueDao.
|
|
6
|
+
*
|
|
7
|
+
* Does NOT support persisting Errors, skips them instead.
|
|
8
|
+
*
|
|
9
|
+
* Also, does not support .clear(), as it's more dangerous than useful to actually
|
|
10
|
+
* clear the whole table/cache.
|
|
11
|
+
*/
|
|
12
|
+
export class CommonKeyValueDaoMemoCache<VALUE = any> implements AsyncMemoCache<string, VALUE> {
|
|
13
|
+
constructor(private dao: CommonKeyValueDao<VALUE>) {}
|
|
14
|
+
|
|
15
|
+
async get(k: string): Promise<VALUE | Error | undefined> {
|
|
16
|
+
return (await this.dao.getById(k)) || undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async set(k: string, v: VALUE | Error): Promise<void> {
|
|
20
|
+
if (v instanceof Error) {
|
|
21
|
+
// We currently don't persist errors there
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await this.dao.save(k, v)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async clear(): Promise<void> {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'CommonKeyValueDaoMemoCache.clear is not supported, because cache is expected to be persistent',
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|