@naturalcycles/db-lib 8.29.0 → 8.33.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.
@@ -39,6 +39,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId>, DBM extends Obj
39
39
  */
40
40
  query(table?: string): RunnableDBQuery<BM, DBM, TM>;
41
41
  runQuery(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<Saved<BM>[]>;
42
+ runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<T[]>;
42
43
  /**
43
44
  * Convenience method that runs multiple queries in parallel and then merges their results together.
44
45
  * Does deduplication by id.
@@ -25,7 +25,8 @@ class CommonDao {
25
25
  // otherwise to log Operations
26
26
  // e.g in Dev (local machine), Test - it will log operations (useful for debugging)
27
27
  logLevel: isGAE || isCI ? common_dao_model_1.CommonDaoLogLevel.NONE : common_dao_model_1.CommonDaoLogLevel.OPERATIONS,
28
- createdUpdated: true,
28
+ created: true,
29
+ updated: true,
29
30
  logger: console,
30
31
  ...cfg,
31
32
  hooks: {
@@ -184,6 +185,12 @@ class CommonDao {
184
185
  const { rows } = await this.runQueryExtended(q, opt);
185
186
  return rows;
186
187
  }
188
+ async runQuerySingleColumn(q, opt) {
189
+ (0, js_lib_1._assert)(q._selectedFieldNames?.length === 1, `runQuerySingleColumn requires exactly 1 column to be selected: ${q.pretty()}`);
190
+ const col = q._selectedFieldNames[0];
191
+ const { rows } = await this.runQueryExtended(q, opt);
192
+ return rows.map(r => r[col]);
193
+ }
187
194
  /**
188
195
  * Convenience method that runs multiple queries in parallel and then merges their results together.
189
196
  * Does deduplication by id.
@@ -399,9 +406,13 @@ class CommonDao {
399
406
  assignIdCreatedUpdated(obj, opt = {}) {
400
407
  const now = Math.floor(Date.now() / 1000);
401
408
  obj.id = obj.id || this.cfg.hooks.createId(obj);
402
- if (this.cfg.createdUpdated) {
409
+ if (this.cfg.created) {
403
410
  Object.assign(obj, {
404
411
  created: obj.created || obj.updated || now,
412
+ });
413
+ }
414
+ if (this.cfg.updated) {
415
+ Object.assign(obj, {
405
416
  updated: opt.preserveUpdatedCreated && obj.updated ? obj.updated : now,
406
417
  });
407
418
  }
@@ -76,10 +76,15 @@ export interface CommonDaoCfg<BM extends Partial<ObjectWithId>, DBM extends Obje
76
76
  logStarted?: boolean;
77
77
  hooks?: Partial<CommonDaoHooks<BM, DBM, TM>>;
78
78
  /**
79
- * @default true
80
- * Set to false to disable created/updated fields management.
79
+ * Defaults to true
80
+ * Set to false to disable `created` field management.
81
81
  */
82
- createdUpdated?: boolean;
82
+ created?: boolean;
83
+ /**
84
+ * Defaults to true
85
+ * Set to false to disable `updated` field management.
86
+ */
87
+ updated?: boolean;
83
88
  /**
84
89
  * Default is false.
85
90
  * If true - will run `_filterNullishValues` inside `validateAndConvert` function
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,9 @@
1
+ import { AsyncMemoCache } from '@naturalcycles/js-lib';
2
+ import { CommonKeyValueDao } from './commonKeyValueDao';
3
+ export declare class CommonKeyValueDaoMemoCache<VALUE = any> implements AsyncMemoCache<string, VALUE> {
4
+ private dao;
5
+ constructor(dao: CommonKeyValueDao<VALUE>);
6
+ get(k: string): Promise<VALUE | undefined>;
7
+ set(k: string, v: VALUE): Promise<void>;
8
+ clear(): Promise<void>;
9
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommonKeyValueDaoMemoCache = void 0;
4
+ class CommonKeyValueDaoMemoCache {
5
+ constructor(dao) {
6
+ this.dao = dao;
7
+ }
8
+ async get(k) {
9
+ return (await this.dao.getById(k)) || undefined;
10
+ }
11
+ async set(k, v) {
12
+ await this.dao.save(k, v);
13
+ }
14
+ async clear() {
15
+ throw new Error('CommonKeyValueDaoCacheFactory.clear is not supported, because cache is expected to be persistent');
16
+ }
17
+ }
18
+ exports.CommonKeyValueDaoMemoCache = CommonKeyValueDaoMemoCache;
@@ -65,6 +65,7 @@ export declare class DBQuery<ROW extends ObjectWithId = AnyObjectWithId> {
65
65
  */
66
66
  _selectedFieldNames?: (keyof ROW)[];
67
67
  _groupByFieldNames?: (keyof ROW)[];
68
+ _distinct: boolean;
68
69
  filter(name: keyof ROW, op: DBQueryFilterOperator, val: any): this;
69
70
  filterEq(name: keyof ROW, val: any): this;
70
71
  limit(limit: number): this;
@@ -72,6 +73,7 @@ export declare class DBQuery<ROW extends ObjectWithId = AnyObjectWithId> {
72
73
  order(name: keyof ROW, descending?: boolean): this;
73
74
  select(fieldNames: (keyof ROW)[]): this;
74
75
  groupBy(fieldNames: (keyof ROW)[]): this;
76
+ distinct(distinct?: boolean): this;
75
77
  startCursor(startCursor?: string): this;
76
78
  endCursor(endCursor?: string): this;
77
79
  clone(): DBQuery<ROW>;
@@ -88,6 +90,7 @@ export declare class RunnableDBQuery<BM extends Partial<ObjectWithId>, DBM exten
88
90
  */
89
91
  constructor(dao: CommonDao<BM, DBM, TM>, table?: string);
90
92
  runQuery(opt?: CommonDaoOptions): Promise<Saved<BM>[]>;
93
+ runQuerySingleColumn<T = any>(opt?: CommonDaoOptions): Promise<T[]>;
91
94
  runQueryAsDBM(opt?: CommonDaoOptions): Promise<DBM[]>;
92
95
  runQueryAsTM(opt?: CommonDaoOptions): Promise<TM[]>;
93
96
  runQueryExtended(opt?: CommonDaoOptions): Promise<RunQueryResult<Saved<BM>>>;
@@ -30,6 +30,7 @@ class DBQuery {
30
30
  this._limitValue = 0; // 0 means "no limit"
31
31
  this._offsetValue = 0; // 0 means "no offset"
32
32
  this._orders = [];
33
+ this._distinct = false;
33
34
  }
34
35
  /**
35
36
  * Convenience method.
@@ -71,6 +72,10 @@ class DBQuery {
71
72
  this._groupByFieldNames = fieldNames;
72
73
  return this;
73
74
  }
75
+ distinct(distinct = true) {
76
+ this._distinct = distinct;
77
+ return this;
78
+ }
74
79
  startCursor(startCursor) {
75
80
  this._startCursor = startCursor;
76
81
  return this;
@@ -86,6 +91,8 @@ class DBQuery {
86
91
  _offsetValue: this._offsetValue,
87
92
  _orders: [...this._orders],
88
93
  _selectedFieldNames: this._selectedFieldNames && [...this._selectedFieldNames],
94
+ _groupByFieldNames: this._groupByFieldNames && [...this._groupByFieldNames],
95
+ _distinct: this._distinct,
89
96
  _startCursor: this._startCursor,
90
97
  _endCursor: this._endCursor,
91
98
  });
@@ -99,7 +106,7 @@ class DBQuery {
99
106
  // tokens.push(`"${this.name}"`)
100
107
  // }
101
108
  if (this._selectedFieldNames) {
102
- tokens.push(`select(${this._selectedFieldNames.join(',')})`);
109
+ tokens.push(`select${this._distinct ? ' distinct' : ''}(${this._selectedFieldNames.join(',')})`);
103
110
  }
104
111
  tokens.push(...this._filters.map(f => `${f.name}${f.op}${f.val}`), ...this._orders.map(o => `order by ${o.name}${o.descending ? ' desc' : ''}`));
105
112
  if (this._groupByFieldNames) {
@@ -135,6 +142,9 @@ class RunnableDBQuery extends DBQuery {
135
142
  async runQuery(opt) {
136
143
  return await this.dao.runQuery(this, opt);
137
144
  }
145
+ async runQuerySingleColumn(opt) {
146
+ return await this.dao.runQuerySingleColumn(this, opt);
147
+ }
138
148
  async runQueryAsDBM(opt) {
139
149
  return await this.dao.runQueryAsDBM(this, opt);
140
150
  }
@@ -17,7 +17,7 @@ export interface TestItemTM {
17
17
  export declare const testItemBMSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<TestItemBM, TestItemBM>;
18
18
  export declare const testItemDBMSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<TestItemDBM, TestItemDBM>;
19
19
  export declare const testItemTMSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<TestItemTM, TestItemTM>;
20
- export declare const testItemBMJsonSchema: import("@naturalcycles/js-lib/dist/json-schema/jsonSchemaBuilder").JsonSchemaObjectBuilder<TestItemBM & Partial<import("@naturalcycles/js-lib").SavedDBEntity>>;
20
+ export declare const testItemBMJsonSchema: import("@naturalcycles/js-lib/dist/json-schema/jsonSchemaBuilder").JsonSchemaObjectBuilder<TestItemBM & Partial<import("@naturalcycles/js-lib").SavedDBEntity<string>>>;
21
21
  export declare const testItemDBMJsonSchema: import("@naturalcycles/js-lib/dist/json-schema/jsonSchemaBuilder").JsonSchemaObjectBuilder<TestItemDBM>;
22
22
  export declare function createTestItemDBM(num?: number): TestItemDBM;
23
23
  export declare function createTestItemBM(num?: number): Saved<TestItemBM>;
@@ -1,8 +1,8 @@
1
1
  import { CommonDBOptions, CommonDBSaveOptions } from '../db.model';
2
2
  import { DBQuery, DBQueryFilter, DBQueryOrder } from '../query/dbQuery';
3
3
  export declare const commonDBOptionsSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<CommonDBOptions, CommonDBOptions>;
4
- export declare const commonDBSaveOptionsSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<CommonDBSaveOptions<import("@naturalcycles/js-lib").AnyObjectWithId>, CommonDBSaveOptions<import("@naturalcycles/js-lib").AnyObjectWithId>>;
4
+ export declare const commonDBSaveOptionsSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<CommonDBSaveOptions<import("@naturalcycles/js-lib").AnyObjectWithId<string>>, CommonDBSaveOptions<import("@naturalcycles/js-lib").AnyObjectWithId<string>>>;
5
5
  export declare const dbQueryFilterOperatorSchema: import("@naturalcycles/nodejs-lib/dist/validation/joi/string.extensions").ExtendedStringSchema;
6
- export declare const dbQueryFilterSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<DBQueryFilter<import("@naturalcycles/js-lib").AnyObjectWithId>, DBQueryFilter<import("@naturalcycles/js-lib").AnyObjectWithId>>;
7
- export declare const dbQueryOrderSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<DBQueryOrder<import("@naturalcycles/js-lib").AnyObjectWithId>, DBQueryOrder<import("@naturalcycles/js-lib").AnyObjectWithId>>;
6
+ export declare const dbQueryFilterSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<DBQueryFilter<import("@naturalcycles/js-lib").AnyObjectWithId<string>>, DBQueryFilter<import("@naturalcycles/js-lib").AnyObjectWithId<string>>>;
7
+ export declare const dbQueryOrderSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<DBQueryOrder<import("@naturalcycles/js-lib").AnyObjectWithId<string>>, DBQueryOrder<import("@naturalcycles/js-lib").AnyObjectWithId<string>>>;
8
8
  export declare const dbQuerySchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<DBQuery<any>, DBQuery<any>>;
package/package.json CHANGED
@@ -43,7 +43,7 @@
43
43
  "engines": {
44
44
  "node": ">=14.15"
45
45
  },
46
- "version": "8.29.0",
46
+ "version": "8.33.0",
47
47
  "description": "Lowest Common Denominator API to supported Databases",
48
48
  "keywords": [
49
49
  "db",
@@ -102,10 +102,16 @@ export interface CommonDaoCfg<
102
102
  hooks?: Partial<CommonDaoHooks<BM, DBM, TM>>
103
103
 
104
104
  /**
105
- * @default true
106
- * Set to false to disable created/updated fields management.
105
+ * Defaults to true
106
+ * Set to false to disable `created` field management.
107
107
  */
108
- createdUpdated?: boolean
108
+ created?: boolean
109
+
110
+ /**
111
+ * Defaults to true
112
+ * Set to false to disable `updated` field management.
113
+ */
114
+ updated?: boolean
109
115
 
110
116
  /**
111
117
  * Default is false.
@@ -1,4 +1,5 @@
1
1
  import {
2
+ _assert,
2
3
  _filterNullishValues,
3
4
  _filterUndefinedValues,
4
5
  _passthroughPredicate,
@@ -68,7 +69,8 @@ export class CommonDao<
68
69
  // otherwise to log Operations
69
70
  // e.g in Dev (local machine), Test - it will log operations (useful for debugging)
70
71
  logLevel: isGAE || isCI ? CommonDaoLogLevel.NONE : CommonDaoLogLevel.OPERATIONS,
71
- createdUpdated: true,
72
+ created: true,
73
+ updated: true,
72
74
  logger: console,
73
75
  ...cfg,
74
76
  hooks: {
@@ -262,6 +264,18 @@ export class CommonDao<
262
264
  return rows
263
265
  }
264
266
 
267
+ async runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<T[]> {
268
+ _assert(
269
+ q._selectedFieldNames?.length === 1,
270
+ `runQuerySingleColumn requires exactly 1 column to be selected: ${q.pretty()}`,
271
+ )
272
+
273
+ const col = q._selectedFieldNames[0]!
274
+
275
+ const { rows } = await this.runQueryExtended(q, opt)
276
+ return rows.map(r => r[col as any])
277
+ }
278
+
265
279
  /**
266
280
  * Convenience method that runs multiple queries in parallel and then merges their results together.
267
281
  * Does deduplication by id.
@@ -549,9 +563,14 @@ export class CommonDao<
549
563
 
550
564
  obj.id = obj.id || this.cfg.hooks!.createId!(obj)
551
565
 
552
- if (this.cfg.createdUpdated) {
566
+ if (this.cfg.created) {
553
567
  Object.assign(obj, {
554
568
  created: (obj as any).created || (obj as any).updated || now,
569
+ })
570
+ }
571
+
572
+ if (this.cfg.updated) {
573
+ Object.assign(obj, {
555
574
  updated: opt.preserveUpdatedCreated && (obj as any).updated ? (obj as any).updated : now,
556
575
  })
557
576
  }
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,20 @@
1
+ import { AsyncMemoCache } from '@naturalcycles/js-lib'
2
+ import { CommonKeyValueDao } from './commonKeyValueDao'
3
+
4
+ export class CommonKeyValueDaoMemoCache<VALUE = any> implements AsyncMemoCache<string, VALUE> {
5
+ constructor(private dao: CommonKeyValueDao<VALUE>) {}
6
+
7
+ async get(k: string): Promise<VALUE | undefined> {
8
+ return (await this.dao.getById(k)) || undefined
9
+ }
10
+
11
+ async set(k: string, v: VALUE): Promise<void> {
12
+ await this.dao.save(k, v)
13
+ }
14
+
15
+ async clear(): Promise<void> {
16
+ throw new Error(
17
+ 'CommonKeyValueDaoCacheFactory.clear is not supported, because cache is expected to be persistent',
18
+ )
19
+ }
20
+ }
@@ -97,6 +97,7 @@ export class DBQuery<ROW extends ObjectWithId = AnyObjectWithId> {
97
97
  */
98
98
  _selectedFieldNames?: (keyof ROW)[]
99
99
  _groupByFieldNames?: (keyof ROW)[]
100
+ _distinct = false
100
101
 
101
102
  filter(name: keyof ROW, op: DBQueryFilterOperator, val: any): this {
102
103
  this._filters.push({ name, op, val })
@@ -136,6 +137,11 @@ export class DBQuery<ROW extends ObjectWithId = AnyObjectWithId> {
136
137
  return this
137
138
  }
138
139
 
140
+ distinct(distinct = true): this {
141
+ this._distinct = distinct
142
+ return this
143
+ }
144
+
139
145
  startCursor(startCursor?: string): this {
140
146
  this._startCursor = startCursor
141
147
  return this
@@ -153,6 +159,8 @@ export class DBQuery<ROW extends ObjectWithId = AnyObjectWithId> {
153
159
  _offsetValue: this._offsetValue,
154
160
  _orders: [...this._orders],
155
161
  _selectedFieldNames: this._selectedFieldNames && [...this._selectedFieldNames],
162
+ _groupByFieldNames: this._groupByFieldNames && [...this._groupByFieldNames],
163
+ _distinct: this._distinct,
156
164
  _startCursor: this._startCursor,
157
165
  _endCursor: this._endCursor,
158
166
  })
@@ -170,7 +178,9 @@ export class DBQuery<ROW extends ObjectWithId = AnyObjectWithId> {
170
178
  // }
171
179
 
172
180
  if (this._selectedFieldNames) {
173
- tokens.push(`select(${this._selectedFieldNames.join(',')})`)
181
+ tokens.push(
182
+ `select${this._distinct ? ' distinct' : ''}(${this._selectedFieldNames.join(',')})`,
183
+ )
174
184
  }
175
185
 
176
186
  tokens.push(
@@ -221,6 +231,10 @@ export class RunnableDBQuery<
221
231
  return await this.dao.runQuery(this, opt)
222
232
  }
223
233
 
234
+ async runQuerySingleColumn<T = any>(opt?: CommonDaoOptions): Promise<T[]> {
235
+ return await this.dao.runQuerySingleColumn<T>(this, opt)
236
+ }
237
+
224
238
  async runQueryAsDBM(opt?: CommonDaoOptions): Promise<DBM[]> {
225
239
  return await this.dao.runQueryAsDBM(this, opt)
226
240
  }