@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 CHANGED
@@ -1,5 +1,6 @@
1
1
  export declare enum DBLibError {
2
2
  DB_ROW_REQUIRED = "DB_ROW_REQUIRED",
3
3
  DAO_IS_READ_ONLY = "DAO_IS_READ_ONLY",
4
- NON_UNIQUE_ID = "NON_UNIQUE_ID"
4
+ NON_UNIQUE_ID = "NON_UNIQUE_ID",
5
+ OBJECT_IS_IMMUTABLE = "OBJECT_IS_IMMUTABLE"
5
6
  }
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
- * Mutates id if needed
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
- const [existing] = await this.cfg.db.getByIds(table, [dbm.id]);
448
- if (existing) {
449
- throw new js_lib_1.AppError(cnst_1.DBLibError.NON_UNIQUE_ID, {
450
- code: cnst_1.DBLibError.NON_UNIQUE_ID,
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
@@ -43,7 +43,7 @@
43
43
  "engines": {
44
44
  "node": ">=14.15"
45
45
  },
46
- "version": "8.31.0",
46
+ "version": "8.34.0",
47
47
  "description": "Lowest Common Denominator API to supported Databases",
48
48
  "keywords": [
49
49
  "db",
package/src/cnst.ts CHANGED
@@ -2,4 +2,5 @@ export enum DBLibError {
2
2
  DB_ROW_REQUIRED = 'DB_ROW_REQUIRED',
3
3
  DAO_IS_READ_ONLY = 'DAO_IS_READ_ONLY',
4
4
  NON_UNIQUE_ID = 'NON_UNIQUE_ID',
5
+ OBJECT_IS_IMMUTABLE = 'OBJECT_IS_IMMUTABLE',
5
6
  }
@@ -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
- * Mutates id if needed
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
- const [existing] = await this.cfg.db.getByIds<DBM>(table, [dbm.id])
609
- if (existing) {
610
- throw new AppError(DBLibError.NON_UNIQUE_ID, {
611
- code: DBLibError.NON_UNIQUE_ID,
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 { ReadableTyped, transformMap } from '@naturalcycles/nodejs-lib'
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
+ }