@naturalcycles/db-lib 9.9.2 → 9.10.1

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.
@@ -361,6 +361,17 @@ class CommonDao {
361
361
  const partialQuery = !!q._selectedFieldNames;
362
362
  if (partialQuery)
363
363
  return stream;
364
+ // This almost works, but hard to implement `errorMode: THROW_AGGREGATED` in this case
365
+ // return stream.flatMap(async (dbm: DBM) => {
366
+ // if (this.cfg.hooks!.afterLoad) {
367
+ // dbm = (await this.cfg.hooks!.afterLoad(dbm))!
368
+ // if (dbm === null) return [] // SKIP
369
+ // }
370
+ //
371
+ // return [await this.dbmToBM(dbm, opt)] satisfies BM[]
372
+ // }, {
373
+ // concurrency: 16,
374
+ // })
364
375
  return (stream
365
376
  // optimization: 1 validation is enough
366
377
  // .pipe(transformMap<any, DBM>(dbm => this.anyToDBM(dbm, opt), safeOpt))
@@ -376,10 +387,11 @@ class CommonDao {
376
387
  }, {
377
388
  errorMode: opt.errorMode,
378
389
  }))
379
- // this can make the stream async-iteration-friendly
380
- // but not applying it now for perf reasons
381
- // .pipe(transformNoOp())
382
- );
390
+ // this can make the stream async-iteration-friendly
391
+ // but not applying it now for perf reasons
392
+ // UPD: applying, to be compliant with `.toArray()`, etc.
393
+ .on('error', err => stream.emit('error', err))
394
+ .pipe((0, nodejs_lib_1.transformNoOp)()));
383
395
  }
384
396
  async queryIds(q, opt = {}) {
385
397
  q.table = opt.table || q.table;
@@ -389,12 +401,19 @@ class CommonDao {
389
401
  streamQueryIds(q, opt = {}) {
390
402
  q.table = opt.table || q.table;
391
403
  opt.errorMode ||= js_lib_1.ErrorMode.SUPPRESS;
404
+ // Experimental: using `.map()`
392
405
  const stream = this.cfg.db
393
406
  .streamQuery(q.select(['id']), opt)
394
- .on('error', err => stream.emit('error', err))
395
- .pipe((0, nodejs_lib_1.transformMapSimple)(r => r.id, {
396
- errorMode: js_lib_1.ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
397
- }));
407
+ // .on('error', err => stream.emit('error', err))
408
+ .map((r) => r.id);
409
+ // const stream: ReadableTyped<string> = this.cfg.db
410
+ // .streamQuery<DBM>(q.select(['id']), opt)
411
+ // .on('error', err => stream.emit('error', err))
412
+ // .pipe(
413
+ // transformMapSimple<DBM, string>(r => r.id, {
414
+ // errorMode: ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
415
+ // }),
416
+ // )
398
417
  return stream;
399
418
  }
400
419
  async streamQueryIdsForEach(q, mapper, opt = {}) {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { KeyValueTuple } from '@naturalcycles/js-lib';
2
+ import { CommonLogger, KeyValueTuple } from '@naturalcycles/js-lib';
3
3
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
4
4
  import { CommonDaoLogLevel } from '../commondao/common.dao.model';
5
5
  import { CommonDBCreateOptions } from '../db.model';
@@ -12,6 +12,10 @@ export interface CommonKeyValueDaoCfg<T> {
12
12
  * Set to true to limit DB writing (will throw an error is such case).
13
13
  */
14
14
  readOnly?: boolean;
15
+ /**
16
+ * Default to console
17
+ */
18
+ logger?: CommonLogger;
15
19
  /**
16
20
  * @default OPERATIONS
17
21
  */
@@ -33,8 +37,11 @@ export interface CommonKeyValueDaoCfg<T> {
33
37
  deflatedJsonValue?: boolean;
34
38
  }
35
39
  export declare class CommonKeyValueDao<T> {
36
- cfg: CommonKeyValueDaoCfg<T>;
37
40
  constructor(cfg: CommonKeyValueDaoCfg<T>);
41
+ cfg: CommonKeyValueDaoCfg<T> & {
42
+ hooks: NonNullable<CommonKeyValueDaoCfg<T>['hooks']>;
43
+ logger: CommonLogger;
44
+ };
38
45
  ping(): Promise<void>;
39
46
  createTable(opt?: CommonDBCreateOptions): Promise<void>;
40
47
  create(input?: Partial<T>): T;
@@ -7,9 +7,13 @@ const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
7
7
  // todo: readonly
8
8
  class CommonKeyValueDao {
9
9
  constructor(cfg) {
10
- this.cfg = cfg;
10
+ this.cfg = {
11
+ hooks: {},
12
+ logger: console,
13
+ ...cfg,
14
+ };
11
15
  if (cfg.deflatedJsonValue) {
12
- cfg.hooks = {
16
+ this.cfg.hooks = {
13
17
  mapValueToBuffer: async (v) => await (0, nodejs_lib_1.deflateString)(JSON.stringify(v)),
14
18
  mapBufferToValue: async (buf) => JSON.parse(await (0, nodejs_lib_1.inflateToString)(buf)),
15
19
  ...cfg.hooks,
@@ -24,7 +28,7 @@ class CommonKeyValueDao {
24
28
  }
25
29
  create(input = {}) {
26
30
  return {
27
- ...this.cfg.hooks?.beforeCreate?.(input),
31
+ ...this.cfg.hooks.beforeCreate?.(input),
28
32
  };
29
33
  }
30
34
  async getById(id) {
@@ -66,7 +70,7 @@ class CommonKeyValueDao {
66
70
  if (r)
67
71
  return r[1];
68
72
  return {
69
- ...this.cfg.hooks?.beforeCreate?.({}),
73
+ ...this.cfg.hooks.beforeCreate?.({}),
70
74
  ...part,
71
75
  };
72
76
  }
@@ -80,7 +84,7 @@ class CommonKeyValueDao {
80
84
  }
81
85
  async getByIds(ids) {
82
86
  const entries = await this.cfg.db.getByIds(this.cfg.table, ids);
83
- if (!this.cfg.hooks?.mapBufferToValue)
87
+ if (!this.cfg.hooks.mapBufferToValue)
84
88
  return entries;
85
89
  return await (0, js_lib_1.pMap)(entries, async ([id, buf]) => [
86
90
  id,
@@ -97,8 +101,8 @@ class CommonKeyValueDao {
97
101
  await this.cfg.db.saveBatch(this.cfg.table, [[id, value]]);
98
102
  }
99
103
  async saveBatch(entries) {
100
- let bufferEntries = [];
101
- if (!this.cfg.hooks?.mapValueToBuffer) {
104
+ let bufferEntries;
105
+ if (!this.cfg.hooks.mapValueToBuffer) {
102
106
  bufferEntries = entries;
103
107
  }
104
108
  else {
@@ -122,29 +126,41 @@ class CommonKeyValueDao {
122
126
  return this.cfg.db.streamIds(this.cfg.table, limit);
123
127
  }
124
128
  streamValues(limit) {
125
- if (!this.cfg.hooks?.mapBufferToValue) {
129
+ const { mapBufferToValue } = this.cfg.hooks;
130
+ if (!mapBufferToValue) {
126
131
  return this.cfg.db.streamValues(this.cfg.table, limit);
127
132
  }
128
- // todo: consider it when readableMap supports `errorMode: SUPPRESS`
129
- // readableMap(this.cfg.db.streamValues(this.cfg.table, limit), async buf => await this.cfg.hooks!.mapBufferToValue(buf))
130
133
  const stream = this.cfg.db
131
134
  .streamValues(this.cfg.table, limit)
132
- .on('error', err => stream.emit('error', err))
133
- .pipe((0, nodejs_lib_1.transformMap)(async (buf) => await this.cfg.hooks.mapBufferToValue(buf), {
134
- errorMode: js_lib_1.ErrorMode.SUPPRESS, // cause .pipe cannot propagate errors
135
- }));
135
+ // .on('error', err => stream.emit('error', err))
136
+ .flatMap(async (buf) => {
137
+ try {
138
+ return [await mapBufferToValue(buf)];
139
+ }
140
+ catch (err) {
141
+ this.cfg.logger.error(err);
142
+ return []; // SKIP
143
+ }
144
+ });
136
145
  return stream;
137
146
  }
138
147
  streamEntries(limit) {
139
- if (!this.cfg.hooks?.mapBufferToValue) {
148
+ const { mapBufferToValue } = this.cfg.hooks;
149
+ if (!mapBufferToValue) {
140
150
  return this.cfg.db.streamEntries(this.cfg.table, limit);
141
151
  }
142
152
  const stream = this.cfg.db
143
153
  .streamEntries(this.cfg.table, limit)
144
- .on('error', err => stream.emit('error', err))
145
- .pipe((0, nodejs_lib_1.transformMap)(async ([id, buf]) => [id, await this.cfg.hooks.mapBufferToValue(buf)], {
146
- errorMode: js_lib_1.ErrorMode.SUPPRESS, // cause .pipe cannot propagate errors
147
- }));
154
+ // .on('error', err => stream.emit('error', err))
155
+ .flatMap(async ([id, buf]) => {
156
+ try {
157
+ return [[id, await mapBufferToValue(buf)]];
158
+ }
159
+ catch (err) {
160
+ this.cfg.logger.error(err);
161
+ return []; // SKIP
162
+ }
163
+ });
148
164
  return stream;
149
165
  }
150
166
  }
@@ -144,9 +144,7 @@ function runCommonDaoTest(db, quirks = {}) {
144
144
  (0, dbTest_1.expectMatch)(expectedItems, rows, quirks);
145
145
  });
146
146
  test('streamQuery all', async () => {
147
- // let rows = await readableToArray(dao.query().streamQuery())
148
- // todo: remove transformNoOp after `transformMap` learns to be async-iteration-friendly
149
- let rows = await (0, nodejs_lib_1.readableToArray)(dao.query().streamQuery().pipe((0, nodejs_lib_1.transformNoOp)()));
147
+ let rows = await dao.query().streamQuery().toArray();
150
148
  rows = (0, js_lib_1._sortBy)(rows, r => r.id);
151
149
  (0, dbTest_1.expectMatch)(expectedItems, rows, quirks);
152
150
  });
@@ -157,7 +155,7 @@ function runCommonDaoTest(db, quirks = {}) {
157
155
  (0, dbTest_1.expectMatch)(expectedItems.map(i => i.id), ids, quirks);
158
156
  });
159
157
  test('streamQueryIds all', async () => {
160
- let ids = await (0, nodejs_lib_1.readableToArray)(dao.query().streamQueryIds());
158
+ let ids = await dao.query().streamQueryIds().toArray();
161
159
  ids = ids.sort();
162
160
  (0, dbTest_1.expectMatch)(expectedItems.map(i => i.id), ids, quirks);
163
161
  });
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.expectMatch = exports.runCommonDBTest = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
- const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
6
5
  const common_db_1 = require("../common.db");
7
6
  const db_model_1 = require("../db.model");
8
7
  const dbQuery_1 = require("../query/dbQuery");
@@ -152,7 +151,7 @@ function runCommonDBTest(db, quirks = {}) {
152
151
  // STREAM
153
152
  if (support.streaming) {
154
153
  test('streamQuery all', async () => {
155
- let rows = await (0, nodejs_lib_1.readableToArray)(db.streamQuery(queryAll()));
154
+ let rows = await db.streamQuery(queryAll()).toArray();
156
155
  rows = (0, js_lib_1._sortBy)(rows, r => r.id); // cause order is not specified in DBQuery
157
156
  expectMatch(items, rows, quirks);
158
157
  });
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runCommonKeyValueDBTest = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
- const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
6
5
  const test_model_1 = require("./test.model");
7
6
  const testIds = (0, js_lib_1._range)(1, 4).map(n => `id${n}`);
8
7
  const testEntries = testIds.map(id => [id, Buffer.from(`${id}value`)]);
@@ -33,35 +32,35 @@ function runCommonKeyValueDBTest(db) {
33
32
  expect(await db.count(test_model_1.TEST_TABLE)).toBe(3);
34
33
  });
35
34
  test('streamIds', async () => {
36
- const ids = await (0, nodejs_lib_1.readableToArray)(db.streamIds(test_model_1.TEST_TABLE));
35
+ const ids = await db.streamIds(test_model_1.TEST_TABLE).toArray();
37
36
  ids.sort();
38
37
  expect(ids).toEqual(testIds);
39
38
  });
40
39
  test('streamIds limited', async () => {
41
- const idsLimited = await (0, nodejs_lib_1.readableToArray)(db.streamIds(test_model_1.TEST_TABLE, 2));
40
+ const idsLimited = await db.streamIds(test_model_1.TEST_TABLE, 2).toArray();
42
41
  // Order is non-deterministic, so, cannot compare values
43
42
  // idsLimited.sort()
44
43
  // expect(idsLimited).toEqual(testIds.slice(0, 2))
45
44
  expect(idsLimited.length).toBe(2);
46
45
  });
47
46
  test('streamValues', async () => {
48
- const values = await (0, nodejs_lib_1.readableToArray)(db.streamValues(test_model_1.TEST_TABLE));
47
+ const values = await db.streamValues(test_model_1.TEST_TABLE).toArray();
49
48
  values.sort();
50
49
  expect(values).toEqual(testEntries.map(e => e[1]));
51
50
  });
52
51
  test('streamValues limited', async () => {
53
- const valuesLimited = await (0, nodejs_lib_1.readableToArray)(db.streamValues(test_model_1.TEST_TABLE, 2));
52
+ const valuesLimited = await db.streamValues(test_model_1.TEST_TABLE, 2).toArray();
54
53
  // valuesLimited.sort()
55
54
  // expect(valuesLimited).toEqual(testEntries.map(e => e[1]).slice(0, 2))
56
55
  expect(valuesLimited.length).toBe(2);
57
56
  });
58
57
  test('streamEntries', async () => {
59
- const entries = await (0, nodejs_lib_1.readableToArray)(db.streamEntries(test_model_1.TEST_TABLE));
58
+ const entries = await db.streamEntries(test_model_1.TEST_TABLE).toArray();
60
59
  entries.sort();
61
60
  expect(entries).toEqual(testEntries);
62
61
  });
63
62
  test('streamEntries limited', async () => {
64
- const entriesLimited = await (0, nodejs_lib_1.readableToArray)(db.streamEntries(test_model_1.TEST_TABLE, 2));
63
+ const entriesLimited = await db.streamEntries(test_model_1.TEST_TABLE, 2).toArray();
65
64
  // entriesLimited.sort()
66
65
  // expect(entriesLimited).toEqual(testEntries.slice(0, 2))
67
66
  expect(entriesLimited.length).toBe(2);
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runCommonKeyValueDaoTest = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
- const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
6
5
  const testIds = (0, js_lib_1._range)(1, 4).map(n => `id${n}`);
7
6
  const testEntries = testIds.map(id => [id, Buffer.from(`${id}value`)]);
8
7
  function runCommonKeyValueDaoTest(dao) {
@@ -26,35 +25,35 @@ function runCommonKeyValueDaoTest(dao) {
26
25
  expect(entries).toEqual(testEntries);
27
26
  });
28
27
  test('streamIds', async () => {
29
- const ids = await (0, nodejs_lib_1.readableToArray)(dao.streamIds());
28
+ const ids = await dao.streamIds().toArray();
30
29
  ids.sort();
31
30
  expect(ids).toEqual(testIds);
32
31
  });
33
32
  test('streamIds limited', async () => {
34
- const idsLimited = await (0, nodejs_lib_1.readableToArray)(dao.streamIds(2));
33
+ const idsLimited = await dao.streamIds(2).toArray();
35
34
  // Order is non-deterministic, so, cannot compare values
36
35
  // idsLimited.sort()
37
36
  // expect(idsLimited).toEqual(testIds.slice(0, 2))
38
37
  expect(idsLimited.length).toBe(2);
39
38
  });
40
39
  test('streamValues', async () => {
41
- const values = await (0, nodejs_lib_1.readableToArray)(dao.streamValues());
40
+ const values = await dao.streamValues().toArray();
42
41
  values.sort();
43
42
  expect(values).toEqual(testEntries.map(e => e[1]));
44
43
  });
45
44
  test('streamValues limited', async () => {
46
- const valuesLimited = await (0, nodejs_lib_1.readableToArray)(dao.streamValues(2));
45
+ const valuesLimited = await dao.streamValues(2).toArray();
47
46
  // valuesLimited.sort()
48
47
  // expect(valuesLimited).toEqual(testEntries.map(e => e[1]).slice(0, 2))
49
48
  expect(valuesLimited.length).toBe(2);
50
49
  });
51
50
  test('streamEntries', async () => {
52
- const entries = await (0, nodejs_lib_1.readableToArray)(dao.streamEntries());
51
+ const entries = await dao.streamEntries().toArray();
53
52
  entries.sort();
54
53
  expect(entries).toEqual(testEntries);
55
54
  });
56
55
  test('streamEntries limited', async () => {
57
- const entriesLimited = await (0, nodejs_lib_1.readableToArray)(dao.streamEntries(2));
56
+ const entriesLimited = await dao.streamEntries(2).toArray();
58
57
  // entriesLimited.sort()
59
58
  // expect(entriesLimited).toEqual(testEntries.slice(0, 2))
60
59
  expect(entriesLimited.length).toBe(2);
package/package.json CHANGED
@@ -40,7 +40,7 @@
40
40
  "engines": {
41
41
  "node": ">=18.12"
42
42
  },
43
- "version": "9.9.2",
43
+ "version": "9.10.1",
44
44
  "description": "Lowest Common Denominator API to supported Databases",
45
45
  "keywords": [
46
46
  "db",
package/readme.md CHANGED
@@ -150,7 +150,7 @@ Returns `ReadableTyped` (typed wrapper of Node.js
150
150
 
151
151
  Streams in Node.js support back-pressure by default (if piped properly by the consumer).
152
152
 
153
- ```typescript
153
+ ```ts
154
154
  const q = DBQuery.create('table1') // "return all items" query
155
155
 
156
156
  await _pipeline([
@@ -165,6 +165,14 @@ await _pipeline([
165
165
  // ...
166
166
  ```
167
167
 
168
+ Alternative:
169
+
170
+ ```ts
171
+ await db.streamQuery(q).forEach(item => {
172
+ console.log(item)
173
+ })
174
+ ```
175
+
168
176
  ###### saveBatch
169
177
 
170
178
  `saveBatch<DBM>(table: string, dbms: DBM[]): Promise<void>`
@@ -40,6 +40,7 @@ import {
40
40
  transformLogProgress,
41
41
  transformMap,
42
42
  transformMapSimple,
43
+ transformNoOp,
43
44
  writableVoid,
44
45
  } from '@naturalcycles/nodejs-lib'
45
46
  import { DBLibError } from '../cnst'
@@ -494,7 +495,19 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
494
495
 
495
496
  const stream = this.cfg.db.streamQuery<DBM>(q, opt)
496
497
  const partialQuery = !!q._selectedFieldNames
497
- if (partialQuery) return stream
498
+ if (partialQuery) return stream as any
499
+
500
+ // This almost works, but hard to implement `errorMode: THROW_AGGREGATED` in this case
501
+ // return stream.flatMap(async (dbm: DBM) => {
502
+ // if (this.cfg.hooks!.afterLoad) {
503
+ // dbm = (await this.cfg.hooks!.afterLoad(dbm))!
504
+ // if (dbm === null) return [] // SKIP
505
+ // }
506
+ //
507
+ // return [await this.dbmToBM(dbm, opt)] satisfies BM[]
508
+ // }, {
509
+ // concurrency: 16,
510
+ // })
498
511
 
499
512
  return (
500
513
  stream
@@ -517,9 +530,11 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
517
530
  },
518
531
  ),
519
532
  )
520
- // this can make the stream async-iteration-friendly
521
- // but not applying it now for perf reasons
522
- // .pipe(transformNoOp())
533
+ // this can make the stream async-iteration-friendly
534
+ // but not applying it now for perf reasons
535
+ // UPD: applying, to be compliant with `.toArray()`, etc.
536
+ .on('error', err => stream.emit('error', err))
537
+ .pipe(transformNoOp())
523
538
  )
524
539
  }
525
540
 
@@ -533,14 +548,20 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
533
548
  q.table = opt.table || q.table
534
549
  opt.errorMode ||= ErrorMode.SUPPRESS
535
550
 
551
+ // Experimental: using `.map()`
536
552
  const stream: ReadableTyped<string> = this.cfg.db
537
553
  .streamQuery<DBM>(q.select(['id']), opt)
538
- .on('error', err => stream.emit('error', err))
539
- .pipe(
540
- transformMapSimple<DBM, string>(r => r.id, {
541
- errorMode: ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
542
- }),
543
- )
554
+ // .on('error', err => stream.emit('error', err))
555
+ .map((r: ObjectWithId) => r.id)
556
+
557
+ // const stream: ReadableTyped<string> = this.cfg.db
558
+ // .streamQuery<DBM>(q.select(['id']), opt)
559
+ // .on('error', err => stream.emit('error', err))
560
+ // .pipe(
561
+ // transformMapSimple<DBM, string>(r => r.id, {
562
+ // errorMode: ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
563
+ // }),
564
+ // )
544
565
 
545
566
  return stream
546
567
  }
@@ -1,10 +1,5 @@
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'
1
+ import { AppError, CommonLogger, KeyValueTuple, pMap } from '@naturalcycles/js-lib'
2
+ import { deflateString, inflateToString, ReadableTyped } from '@naturalcycles/nodejs-lib'
8
3
  import { CommonDaoLogLevel } from '../commondao/common.dao.model'
9
4
  import { CommonDBCreateOptions } from '../db.model'
10
5
  import { CommonKeyValueDB, KeyValueDBTuple } from './commonKeyValueDB'
@@ -20,6 +15,11 @@ export interface CommonKeyValueDaoCfg<T> {
20
15
  */
21
16
  readOnly?: boolean
22
17
 
18
+ /**
19
+ * Default to console
20
+ */
21
+ logger?: CommonLogger
22
+
23
23
  /**
24
24
  * @default OPERATIONS
25
25
  */
@@ -48,9 +48,15 @@ export interface CommonKeyValueDaoCfg<T> {
48
48
  // todo: readonly
49
49
 
50
50
  export class CommonKeyValueDao<T> {
51
- constructor(public cfg: CommonKeyValueDaoCfg<T>) {
51
+ constructor(cfg: CommonKeyValueDaoCfg<T>) {
52
+ this.cfg = {
53
+ hooks: {},
54
+ logger: console,
55
+ ...cfg,
56
+ }
57
+
52
58
  if (cfg.deflatedJsonValue) {
53
- cfg.hooks = {
59
+ this.cfg.hooks = {
54
60
  mapValueToBuffer: async v => await deflateString(JSON.stringify(v)),
55
61
  mapBufferToValue: async buf => JSON.parse(await inflateToString(buf)),
56
62
  ...cfg.hooks,
@@ -58,6 +64,11 @@ export class CommonKeyValueDao<T> {
58
64
  }
59
65
  }
60
66
 
67
+ cfg: CommonKeyValueDaoCfg<T> & {
68
+ hooks: NonNullable<CommonKeyValueDaoCfg<T>['hooks']>
69
+ logger: CommonLogger
70
+ }
71
+
61
72
  async ping(): Promise<void> {
62
73
  await this.cfg.db.ping()
63
74
  }
@@ -68,7 +79,7 @@ export class CommonKeyValueDao<T> {
68
79
 
69
80
  create(input: Partial<T> = {}): T {
70
81
  return {
71
- ...this.cfg.hooks?.beforeCreate?.(input),
82
+ ...this.cfg.hooks.beforeCreate?.(input),
72
83
  } as T
73
84
  }
74
85
 
@@ -117,7 +128,7 @@ export class CommonKeyValueDao<T> {
117
128
  if (r) return r[1]
118
129
 
119
130
  return {
120
- ...this.cfg.hooks?.beforeCreate?.({}),
131
+ ...this.cfg.hooks.beforeCreate?.({}),
121
132
  ...part,
122
133
  } as T
123
134
  }
@@ -135,11 +146,11 @@ export class CommonKeyValueDao<T> {
135
146
 
136
147
  async getByIds(ids: string[]): Promise<KeyValueTuple<string, T>[]> {
137
148
  const entries = await this.cfg.db.getByIds(this.cfg.table, ids)
138
- if (!this.cfg.hooks?.mapBufferToValue) return entries as any
149
+ if (!this.cfg.hooks.mapBufferToValue) return entries as any
139
150
 
140
151
  return await pMap(entries, async ([id, buf]) => [
141
152
  id,
142
- await this.cfg.hooks!.mapBufferToValue!(buf),
153
+ await this.cfg.hooks.mapBufferToValue!(buf),
143
154
  ])
144
155
  }
145
156
 
@@ -156,14 +167,14 @@ export class CommonKeyValueDao<T> {
156
167
  }
157
168
 
158
169
  async saveBatch(entries: KeyValueTuple<string, T>[]): Promise<void> {
159
- let bufferEntries: KeyValueDBTuple[] = []
170
+ let bufferEntries: KeyValueDBTuple[]
160
171
 
161
- if (!this.cfg.hooks?.mapValueToBuffer) {
172
+ if (!this.cfg.hooks.mapValueToBuffer) {
162
173
  bufferEntries = entries as any
163
174
  } else {
164
175
  bufferEntries = await pMap(entries, async ([id, v]) => [
165
176
  id,
166
- await this.cfg.hooks!.mapValueToBuffer!(v),
177
+ await this.cfg.hooks.mapValueToBuffer!(v),
167
178
  ])
168
179
  }
169
180
 
@@ -187,37 +198,47 @@ export class CommonKeyValueDao<T> {
187
198
  }
188
199
 
189
200
  streamValues(limit?: number): ReadableTyped<T> {
190
- if (!this.cfg.hooks?.mapBufferToValue) {
191
- return this.cfg.db.streamValues(this.cfg.table, limit)
201
+ const { mapBufferToValue } = this.cfg.hooks
202
+
203
+ if (!mapBufferToValue) {
204
+ return this.cfg.db.streamValues(this.cfg.table, limit) as ReadableTyped<T>
192
205
  }
193
206
 
194
- // todo: consider it when readableMap supports `errorMode: SUPPRESS`
195
- // readableMap(this.cfg.db.streamValues(this.cfg.table, limit), async buf => await this.cfg.hooks!.mapBufferToValue(buf))
196
207
  const stream: ReadableTyped<T> = this.cfg.db
197
208
  .streamValues(this.cfg.table, limit)
198
- .on('error', err => stream.emit('error', err))
199
- .pipe(
200
- transformMap(async buf => await this.cfg.hooks!.mapBufferToValue!(buf), {
201
- errorMode: ErrorMode.SUPPRESS, // cause .pipe cannot propagate errors
202
- }),
203
- )
209
+ // .on('error', err => stream.emit('error', err))
210
+ .flatMap(async buf => {
211
+ try {
212
+ return [await mapBufferToValue(buf)]
213
+ } catch (err) {
214
+ this.cfg.logger.error(err)
215
+ return [] // SKIP
216
+ }
217
+ })
204
218
 
205
219
  return stream
206
220
  }
207
221
 
208
222
  streamEntries(limit?: number): ReadableTyped<KeyValueTuple<string, T>> {
209
- if (!this.cfg.hooks?.mapBufferToValue) {
210
- return this.cfg.db.streamEntries(this.cfg.table, limit)
223
+ const { mapBufferToValue } = this.cfg.hooks
224
+
225
+ if (!mapBufferToValue) {
226
+ return this.cfg.db.streamEntries(this.cfg.table, limit) as ReadableTyped<
227
+ KeyValueTuple<string, T>
228
+ >
211
229
  }
212
230
 
213
231
  const stream: ReadableTyped<KeyValueTuple<string, T>> = this.cfg.db
214
232
  .streamEntries(this.cfg.table, limit)
215
- .on('error', err => stream.emit('error', err))
216
- .pipe(
217
- transformMap(async ([id, buf]) => [id, await this.cfg.hooks!.mapBufferToValue!(buf)], {
218
- errorMode: ErrorMode.SUPPRESS, // cause .pipe cannot propagate errors
219
- }),
220
- )
233
+ // .on('error', err => stream.emit('error', err))
234
+ .flatMap(async ([id, buf]) => {
235
+ try {
236
+ return [[id, await mapBufferToValue(buf)]]
237
+ } catch (err) {
238
+ this.cfg.logger.error(err)
239
+ return [] // SKIP
240
+ }
241
+ })
221
242
 
222
243
  return stream
223
244
  }
@@ -1,6 +1,6 @@
1
1
  import { Readable } from 'node:stream'
2
2
  import { _deepCopy, _pick, _sortBy, _omit, localTimeNow } from '@naturalcycles/js-lib'
3
- import { _pipeline, readableToArray, transformNoOp } from '@naturalcycles/nodejs-lib'
3
+ import { _pipeline } from '@naturalcycles/nodejs-lib'
4
4
  import { CommonDaoLogLevel, DBQuery } from '..'
5
5
  import { CommonDB } from '../common.db'
6
6
  import { CommonDao } from '../commondao/common.dao'
@@ -194,11 +194,7 @@ export function runCommonDaoTest(db: CommonDB, quirks: CommonDBImplementationQui
194
194
  })
195
195
 
196
196
  test('streamQuery all', async () => {
197
- // let rows = await readableToArray(dao.query().streamQuery())
198
- // todo: remove transformNoOp after `transformMap` learns to be async-iteration-friendly
199
- let rows: TestItemBM[] = await readableToArray(
200
- dao.query().streamQuery().pipe(transformNoOp()),
201
- )
197
+ let rows: TestItemBM[] = await dao.query().streamQuery().toArray()
202
198
 
203
199
  rows = _sortBy(rows, r => r.id)
204
200
  expectMatch(expectedItems, rows, quirks)
@@ -216,7 +212,7 @@ export function runCommonDaoTest(db: CommonDB, quirks: CommonDBImplementationQui
216
212
  })
217
213
 
218
214
  test('streamQueryIds all', async () => {
219
- let ids = await readableToArray(dao.query().streamQueryIds())
215
+ let ids = await dao.query().streamQueryIds().toArray()
220
216
  ids = ids.sort()
221
217
  expectMatch(
222
218
  expectedItems.map(i => i.id),
@@ -1,5 +1,4 @@
1
1
  import { _filterObject, _pick, _sortBy, pMap } from '@naturalcycles/js-lib'
2
- import { readableToArray } from '@naturalcycles/nodejs-lib'
3
2
  import { CommonDB, CommonDBType } from '../common.db'
4
3
  import { DBIncrement, DBPatch } from '../db.model'
5
4
  import { DBQuery } from '../query/dbQuery'
@@ -220,7 +219,7 @@ export function runCommonDBTest(db: CommonDB, quirks: CommonDBImplementationQuir
220
219
  // STREAM
221
220
  if (support.streaming) {
222
221
  test('streamQuery all', async () => {
223
- let rows = await readableToArray(db.streamQuery(queryAll()))
222
+ let rows = await db.streamQuery(queryAll()).toArray()
224
223
 
225
224
  rows = _sortBy(rows, r => r.id) // cause order is not specified in DBQuery
226
225
  expectMatch(items, rows, quirks)
@@ -1,5 +1,4 @@
1
1
  import { _range, _sortBy } from '@naturalcycles/js-lib'
2
- import { readableToArray } from '@naturalcycles/nodejs-lib'
3
2
  import { CommonKeyValueDB, KeyValueDBTuple } from '../kv/commonKeyValueDB'
4
3
  import { TEST_TABLE } from './test.model'
5
4
 
@@ -42,13 +41,13 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
42
41
  })
43
42
 
44
43
  test('streamIds', async () => {
45
- const ids = await readableToArray(db.streamIds(TEST_TABLE))
44
+ const ids = await db.streamIds(TEST_TABLE).toArray()
46
45
  ids.sort()
47
46
  expect(ids).toEqual(testIds)
48
47
  })
49
48
 
50
49
  test('streamIds limited', async () => {
51
- const idsLimited = await readableToArray(db.streamIds(TEST_TABLE, 2))
50
+ const idsLimited = await db.streamIds(TEST_TABLE, 2).toArray()
52
51
  // Order is non-deterministic, so, cannot compare values
53
52
  // idsLimited.sort()
54
53
  // expect(idsLimited).toEqual(testIds.slice(0, 2))
@@ -56,26 +55,26 @@ export function runCommonKeyValueDBTest(db: CommonKeyValueDB): void {
56
55
  })
57
56
 
58
57
  test('streamValues', async () => {
59
- const values = await readableToArray(db.streamValues(TEST_TABLE))
58
+ const values = await db.streamValues(TEST_TABLE).toArray()
60
59
  values.sort()
61
60
  expect(values).toEqual(testEntries.map(e => e[1]))
62
61
  })
63
62
 
64
63
  test('streamValues limited', async () => {
65
- const valuesLimited = await readableToArray(db.streamValues(TEST_TABLE, 2))
64
+ const valuesLimited = await db.streamValues(TEST_TABLE, 2).toArray()
66
65
  // valuesLimited.sort()
67
66
  // expect(valuesLimited).toEqual(testEntries.map(e => e[1]).slice(0, 2))
68
67
  expect(valuesLimited.length).toBe(2)
69
68
  })
70
69
 
71
70
  test('streamEntries', async () => {
72
- const entries = await readableToArray(db.streamEntries(TEST_TABLE))
71
+ const entries = await db.streamEntries(TEST_TABLE).toArray()
73
72
  entries.sort()
74
73
  expect(entries).toEqual(testEntries)
75
74
  })
76
75
 
77
76
  test('streamEntries limited', async () => {
78
- const entriesLimited = await readableToArray(db.streamEntries(TEST_TABLE, 2))
77
+ const entriesLimited = await db.streamEntries(TEST_TABLE, 2).toArray()
79
78
  // entriesLimited.sort()
80
79
  // expect(entriesLimited).toEqual(testEntries.slice(0, 2))
81
80
  expect(entriesLimited.length).toBe(2)
@@ -1,5 +1,4 @@
1
1
  import { _range, _sortBy } from '@naturalcycles/js-lib'
2
- import { readableToArray } from '@naturalcycles/nodejs-lib'
3
2
  import { KeyValueDBTuple } from '../kv/commonKeyValueDB'
4
3
  import { CommonKeyValueDao } from '../kv/commonKeyValueDao'
5
4
 
@@ -33,13 +32,13 @@ export function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void {
33
32
  })
34
33
 
35
34
  test('streamIds', async () => {
36
- const ids = await readableToArray(dao.streamIds())
35
+ const ids = await dao.streamIds().toArray()
37
36
  ids.sort()
38
37
  expect(ids).toEqual(testIds)
39
38
  })
40
39
 
41
40
  test('streamIds limited', async () => {
42
- const idsLimited = await readableToArray(dao.streamIds(2))
41
+ const idsLimited = await dao.streamIds(2).toArray()
43
42
  // Order is non-deterministic, so, cannot compare values
44
43
  // idsLimited.sort()
45
44
  // expect(idsLimited).toEqual(testIds.slice(0, 2))
@@ -47,26 +46,26 @@ export function runCommonKeyValueDaoTest(dao: CommonKeyValueDao<Buffer>): void {
47
46
  })
48
47
 
49
48
  test('streamValues', async () => {
50
- const values = await readableToArray(dao.streamValues())
49
+ const values = await dao.streamValues().toArray()
51
50
  values.sort()
52
51
  expect(values).toEqual(testEntries.map(e => e[1]))
53
52
  })
54
53
 
55
54
  test('streamValues limited', async () => {
56
- const valuesLimited = await readableToArray(dao.streamValues(2))
55
+ const valuesLimited = await dao.streamValues(2).toArray()
57
56
  // valuesLimited.sort()
58
57
  // expect(valuesLimited).toEqual(testEntries.map(e => e[1]).slice(0, 2))
59
58
  expect(valuesLimited.length).toBe(2)
60
59
  })
61
60
 
62
61
  test('streamEntries', async () => {
63
- const entries = await readableToArray(dao.streamEntries())
62
+ const entries = await dao.streamEntries().toArray()
64
63
  entries.sort()
65
64
  expect(entries).toEqual(testEntries)
66
65
  })
67
66
 
68
67
  test('streamEntries limited', async () => {
69
- const entriesLimited = await readableToArray(dao.streamEntries(2))
68
+ const entriesLimited = await dao.streamEntries(2).toArray()
70
69
  // entriesLimited.sort()
71
70
  // expect(entriesLimited).toEqual(testEntries.slice(0, 2))
72
71
  expect(entriesLimited.length).toBe(2)