@naturalcycles/db-lib 8.33.0 → 8.34.2

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 ensureImmutableDontExist;
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.ensureImmutableDontExist(table, [dbm.id]);
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,29 @@ class CommonDao {
439
452
  this.logSaveResult(started, op, table);
440
453
  return bm;
441
454
  }
442
- /**
443
- * Mutates id if needed
444
- */
455
+ async ensureImmutableDontExist(table, ids) {
456
+ await this.throwIfObjectExists(table, ids, [
457
+ cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
458
+ {
459
+ code: cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
460
+ table,
461
+ },
462
+ ]);
463
+ }
445
464
  async ensureUniqueId(table, dbm) {
446
465
  // 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,
451
- id: dbm.id,
466
+ await this.throwIfObjectExists(table, [dbm.id], [
467
+ cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
468
+ {
469
+ code: cnst_1.DBLibError.OBJECT_IS_IMMUTABLE,
452
470
  table,
453
- });
454
- }
471
+ },
472
+ ]);
473
+ }
474
+ async throwIfObjectExists(table, ids, errorMeta) {
475
+ const existing = await this.cfg.db.getByIds(table, ids);
476
+ if (existing.length > 0)
477
+ throw new js_lib_1.AppError(errorMeta[0], { ...errorMeta[1], ids: existing.map(i => i.id) });
455
478
  }
456
479
  /**
457
480
  * Loads the row by id.
@@ -486,6 +509,8 @@ class CommonDao {
486
509
  dbm = this.anyToDBM(dbm, opt);
487
510
  if (opt.ensureUniqueId && idWasGenerated)
488
511
  await this.ensureUniqueId(table, dbm);
512
+ if (this.cfg.immutable)
513
+ await this.ensureImmutableDontExist(table, [dbm.id]);
489
514
  }
490
515
  const op = `saveAsDBM(${dbm.id})`;
491
516
  const started = this.logSaveStarted(op, dbm, table);
@@ -503,6 +528,8 @@ class CommonDao {
503
528
  const dbms = await this.bmsToDBM(bms, opt);
504
529
  if (opt.ensureUniqueId)
505
530
  throw new js_lib_1.AppError('ensureUniqueId is not supported in saveBatch');
531
+ if (this.cfg.immutable)
532
+ await this.ensureImmutableDontExist(table, dbms.map(dbm => dbm.id));
506
533
  const op = `saveBatch ${dbms.length} row(s) (${(0, js_lib_1._truncate)(dbms
507
534
  .slice(0, 10)
508
535
  .map(bm => bm.id)
@@ -523,6 +550,8 @@ class CommonDao {
523
550
  dbms = this.anyToDBMs(dbms, opt);
524
551
  if (opt.ensureUniqueId)
525
552
  throw new js_lib_1.AppError('ensureUniqueId is not supported in saveBatch');
553
+ if (this.cfg.immutable)
554
+ await this.ensureImmutableDontExist(table, dbms.map(dbm => dbm.id));
526
555
  }
527
556
  const op = `saveBatchAsDBM ${dbms.length} row(s) (${(0, js_lib_1._truncate)(dbms
528
557
  .slice(0, 10)
@@ -540,6 +569,8 @@ class CommonDao {
540
569
  if (!id)
541
570
  return 0;
542
571
  this.requireWriteAccess();
572
+ if (!opt.allowMutability)
573
+ this.requireObjectMutability();
543
574
  const op = `deleteById(${id})`;
544
575
  const table = opt.table || this.cfg.table;
545
576
  const started = this.logStarted(op, table);
@@ -549,6 +580,8 @@ class CommonDao {
549
580
  }
550
581
  async deleteByIds(ids, opt = {}) {
551
582
  this.requireWriteAccess();
583
+ if (!opt.allowMutability)
584
+ this.requireObjectMutability();
552
585
  const op = `deleteByIds(${ids.join(', ')})`;
553
586
  const table = opt.table || this.cfg.table;
554
587
  const started = this.logStarted(op, table);
@@ -563,6 +596,8 @@ class CommonDao {
563
596
  */
564
597
  async deleteByQuery(q, opt = {}) {
565
598
  this.requireWriteAccess();
599
+ if (!opt.allowMutability)
600
+ this.requireObjectMutability();
566
601
  q.table = opt.table || q.table;
567
602
  const op = `deleteByQuery(${q.pretty()})`;
568
603
  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
+ allowMutability?: 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).
@@ -1,9 +1,17 @@
1
1
  import { AsyncMemoCache } from '@naturalcycles/js-lib';
2
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
+ */
3
11
  export declare class CommonKeyValueDaoMemoCache<VALUE = any> implements AsyncMemoCache<string, VALUE> {
4
12
  private dao;
5
13
  constructor(dao: CommonKeyValueDao<VALUE>);
6
- get(k: string): Promise<VALUE | undefined>;
7
- set(k: string, v: VALUE): Promise<void>;
14
+ get(k: string): Promise<VALUE | Error | undefined>;
15
+ set(k: string, v: VALUE | Error): Promise<void>;
8
16
  clear(): Promise<void>;
9
17
  }
@@ -1,6 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
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
+ */
4
12
  class CommonKeyValueDaoMemoCache {
5
13
  constructor(dao) {
6
14
  this.dao = dao;
@@ -9,10 +17,14 @@ class CommonKeyValueDaoMemoCache {
9
17
  return (await this.dao.getById(k)) || undefined;
10
18
  }
11
19
  async set(k, v) {
20
+ if (v instanceof Error) {
21
+ // We currently don't persist errors there
22
+ return;
23
+ }
12
24
  await this.dao.save(k, v);
13
25
  }
14
26
  async clear() {
15
- throw new Error('CommonKeyValueDaoCacheFactory.clear is not supported, because cache is expected to be persistent');
27
+ throw new Error('CommonKeyValueDaoMemoCache.clear is not supported, because cache is expected to be persistent');
16
28
  }
17
29
  }
18
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.33.0",
46
+ "version": "8.34.2",
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
+ allowMutability?: 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.ensureImmutableDontExist(table, [dbm.id])
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,39 @@ export class CommonDao<
600
613
  return bm as any
601
614
  }
602
615
 
603
- /**
604
- * Mutates id if needed
605
- */
616
+ private async ensureImmutableDontExist(table: string, ids: string[]): Promise<void> {
617
+ await this.throwIfObjectExists(table, ids, [
618
+ DBLibError.OBJECT_IS_IMMUTABLE,
619
+ {
620
+ code: DBLibError.OBJECT_IS_IMMUTABLE,
621
+ table,
622
+ },
623
+ ])
624
+ }
625
+
606
626
  private async ensureUniqueId(table: string, dbm: DBM): Promise<void> {
607
627
  // 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,
612
- id: dbm.id,
613
- table,
614
- })
615
- }
628
+ await this.throwIfObjectExists(
629
+ table,
630
+ [dbm.id],
631
+ [
632
+ DBLibError.OBJECT_IS_IMMUTABLE,
633
+ {
634
+ code: DBLibError.OBJECT_IS_IMMUTABLE,
635
+ table,
636
+ },
637
+ ],
638
+ )
639
+ }
640
+
641
+ private async throwIfObjectExists(
642
+ table: string,
643
+ ids: string[],
644
+ errorMeta: [DBLibError, any],
645
+ ): Promise<void> {
646
+ const existing = await this.cfg.db.getByIds<DBM>(table, ids)
647
+ if (existing.length > 0)
648
+ throw new AppError(errorMeta[0], { ...errorMeta[1], ids: existing.map(i => i.id) })
616
649
  }
617
650
 
618
651
  /**
@@ -666,6 +699,7 @@ export class CommonDao<
666
699
  this.assignIdCreatedUpdated(dbm, opt) // mutates
667
700
  dbm = this.anyToDBM(dbm, opt)
668
701
  if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
702
+ if (this.cfg.immutable) await this.ensureImmutableDontExist(table, [dbm.id])
669
703
  }
670
704
  const op = `saveAsDBM(${dbm.id})`
671
705
  const started = this.logSaveStarted(op, dbm, table)
@@ -683,6 +717,12 @@ export class CommonDao<
683
717
  bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt))
684
718
  const dbms = await this.bmsToDBM(bms, opt)
685
719
  if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
720
+ if (this.cfg.immutable)
721
+ await this.ensureImmutableDontExist(
722
+ table,
723
+ dbms.map(dbm => dbm.id),
724
+ )
725
+
686
726
  const op = `saveBatch ${dbms.length} row(s) (${_truncate(
687
727
  dbms
688
728
  .slice(0, 10)
@@ -709,6 +749,11 @@ export class CommonDao<
709
749
  dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)) // mutates
710
750
  dbms = this.anyToDBMs(dbms, opt)
711
751
  if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
752
+ if (this.cfg.immutable)
753
+ await this.ensureImmutableDontExist(
754
+ table,
755
+ dbms.map(dbm => dbm.id),
756
+ )
712
757
  }
713
758
  const op = `saveBatchAsDBM ${dbms.length} row(s) (${_truncate(
714
759
  dbms
@@ -737,6 +782,7 @@ export class CommonDao<
737
782
  async deleteById(id?: string, opt: CommonDaoOptions = {}): Promise<number> {
738
783
  if (!id) return 0
739
784
  this.requireWriteAccess()
785
+ if (!opt.allowMutability) this.requireObjectMutability()
740
786
  const op = `deleteById(${id})`
741
787
  const table = opt.table || this.cfg.table
742
788
  const started = this.logStarted(op, table)
@@ -747,6 +793,7 @@ export class CommonDao<
747
793
 
748
794
  async deleteByIds(ids: string[], opt: CommonDaoOptions = {}): Promise<number> {
749
795
  this.requireWriteAccess()
796
+ if (!opt.allowMutability) this.requireObjectMutability()
750
797
  const op = `deleteByIds(${ids.join(', ')})`
751
798
  const table = opt.table || this.cfg.table
752
799
  const started = this.logStarted(op, table)
@@ -765,6 +812,7 @@ export class CommonDao<
765
812
  opt: CommonDaoStreamForEachOptions<DBM> & { stream?: boolean } = {},
766
813
  ): Promise<number> {
767
814
  this.requireWriteAccess()
815
+ if (!opt.allowMutability) this.requireObjectMutability()
768
816
  q.table = opt.table || q.table
769
817
  const op = `deleteByQuery(${q.pretty()})`
770
818
  const started = this.logStarted(op, q.table)
@@ -1,20 +1,33 @@
1
1
  import { AsyncMemoCache } from '@naturalcycles/js-lib'
2
2
  import { CommonKeyValueDao } from './commonKeyValueDao'
3
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
+ */
4
12
  export class CommonKeyValueDaoMemoCache<VALUE = any> implements AsyncMemoCache<string, VALUE> {
5
13
  constructor(private dao: CommonKeyValueDao<VALUE>) {}
6
14
 
7
- async get(k: string): Promise<VALUE | undefined> {
15
+ async get(k: string): Promise<VALUE | Error | undefined> {
8
16
  return (await this.dao.getById(k)) || undefined
9
17
  }
10
18
 
11
- async set(k: string, v: VALUE): Promise<void> {
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
+
12
25
  await this.dao.save(k, v)
13
26
  }
14
27
 
15
28
  async clear(): Promise<void> {
16
29
  throw new Error(
17
- 'CommonKeyValueDaoCacheFactory.clear is not supported, because cache is expected to be persistent',
30
+ 'CommonKeyValueDaoMemoCache.clear is not supported, because cache is expected to be persistent',
18
31
  )
19
32
  }
20
33
  }