Skip to content
This repository was archived by the owner on May 25, 2025. It is now read-only.

Commit 0910865

Browse files
feat: refactor to use stream iterator functions like map
CommonDao.stream* functions are now iteration-friendly, can be used with `.toArray()` and such
1 parent be4dbff commit 0910865

File tree

6 files changed

+100
-53
lines changed

6 files changed

+100
-53
lines changed

readme.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Returns `ReadableTyped` (typed wrapper of Node.js
150150
151151
Streams in Node.js support back-pressure by default (if piped properly by the consumer).
152152
153-
```typescript
153+
```ts
154154
const q = DBQuery.create('table1') // "return all items" query
155155

156156
await _pipeline([
@@ -165,6 +165,14 @@ await _pipeline([
165165
// ...
166166
```
167167
168+
Alternative:
169+
170+
```ts
171+
await db.streamQuery(q).forEach(item => {
172+
console.log(item)
173+
})
174+
```
175+
168176
###### saveBatch
169177
170178
`saveBatch<DBM>(table: string, dbms: DBM[]): Promise<void>`

src/commondao/common.dao.test.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import {
1212
import {
1313
AjvSchema,
1414
AjvValidationError,
15-
writableForEach,
16-
_pipeline,
1715
deflateString,
1816
inflateToString,
1917
} from '@naturalcycles/nodejs-lib'
@@ -29,7 +27,12 @@ import {
2927
createTestItemBM,
3028
} from '../testing'
3129
import { CommonDao } from './common.dao'
32-
import { CommonDaoCfg, CommonDaoLogLevel, CommonDaoSaveBatchOptions } from './common.dao.model'
30+
import {
31+
CommonDaoCfg,
32+
CommonDaoLogLevel,
33+
CommonDaoOptions,
34+
CommonDaoSaveBatchOptions,
35+
} from './common.dao.model'
3336

3437
let throwError = false
3538

@@ -102,7 +105,7 @@ test('should propagate pipe errors', async () => {
102105

103106
throwError = true
104107

105-
const opt = {
108+
const opt: CommonDaoOptions = {
106109
// logEvery: 1,
107110
}
108111

@@ -145,8 +148,8 @@ test('should propagate pipe errors', async () => {
145148
expect(results).toEqual(items.filter(i => i.id !== 'id3'))
146149

147150
// .stream should suppress by default
148-
results = []
149-
await _pipeline([dao.query().streamQuery(opt), writableForEach(r => void results.push(r))])
151+
results = await dao.query().streamQuery(opt).toArray()
152+
150153
expect(results).toEqual(items.filter(i => i.id !== 'id3'))
151154
})
152155

src/commondao/common.dao.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
transformLogProgress,
4141
transformMap,
4242
transformMapSimple,
43+
transformNoOp,
4344
writableVoid,
4445
} from '@naturalcycles/nodejs-lib'
4546
import { DBLibError } from '../cnst'
@@ -496,6 +497,18 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
496497
const partialQuery = !!q._selectedFieldNames
497498
if (partialQuery) return stream
498499

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+
// })
511+
499512
return (
500513
stream
501514
// optimization: 1 validation is enough
@@ -517,9 +530,11 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
517530
},
518531
),
519532
)
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())
523538
)
524539
}
525540

@@ -533,14 +548,20 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM> {
533548
q.table = opt.table || q.table
534549
opt.errorMode ||= ErrorMode.SUPPRESS
535550

551+
// Experimental: using `.map()`
536552
const stream: ReadableTyped<string> = this.cfg.db
537553
.streamQuery<DBM>(q.select(['id']), opt)
538554
.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-
)
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+
// )
544565

545566
return stream
546567
}

src/kv/commonKeyValueDao.ts

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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'
83
import { CommonDaoLogLevel } from '../commondao/common.dao.model'
94
import { CommonDBCreateOptions } from '../db.model'
105
import { CommonKeyValueDB, KeyValueDBTuple } from './commonKeyValueDB'
@@ -20,6 +15,11 @@ export interface CommonKeyValueDaoCfg<T> {
2015
*/
2116
readOnly?: boolean
2217

18+
/**
19+
* Default to console
20+
*/
21+
logger?: CommonLogger
22+
2323
/**
2424
* @default OPERATIONS
2525
*/
@@ -48,16 +48,27 @@ export interface CommonKeyValueDaoCfg<T> {
4848
// todo: readonly
4949

5050
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+
5258
if (cfg.deflatedJsonValue) {
53-
cfg.hooks = {
59+
this.cfg.hooks = {
5460
mapValueToBuffer: async v => await deflateString(JSON.stringify(v)),
5561
mapBufferToValue: async buf => JSON.parse(await inflateToString(buf)),
5662
...cfg.hooks,
5763
}
5864
}
5965
}
6066

67+
cfg: CommonKeyValueDaoCfg<T> & {
68+
hooks: NonNullable<CommonKeyValueDaoCfg<T>['hooks']>
69+
logger: CommonLogger
70+
}
71+
6172
async ping(): Promise<void> {
6273
await this.cfg.db.ping()
6374
}
@@ -68,7 +79,7 @@ export class CommonKeyValueDao<T> {
6879

6980
create(input: Partial<T> = {}): T {
7081
return {
71-
...this.cfg.hooks?.beforeCreate?.(input),
82+
...this.cfg.hooks.beforeCreate?.(input),
7283
} as T
7384
}
7485

@@ -117,7 +128,7 @@ export class CommonKeyValueDao<T> {
117128
if (r) return r[1]
118129

119130
return {
120-
...this.cfg.hooks?.beforeCreate?.({}),
131+
...this.cfg.hooks.beforeCreate?.({}),
121132
...part,
122133
} as T
123134
}
@@ -135,11 +146,11 @@ export class CommonKeyValueDao<T> {
135146

136147
async getByIds(ids: string[]): Promise<KeyValueTuple<string, T>[]> {
137148
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
139150

140151
return await pMap(entries, async ([id, buf]) => [
141152
id,
142-
await this.cfg.hooks!.mapBufferToValue!(buf),
153+
await this.cfg.hooks.mapBufferToValue!(buf),
143154
])
144155
}
145156

@@ -158,12 +169,12 @@ export class CommonKeyValueDao<T> {
158169
async saveBatch(entries: KeyValueTuple<string, T>[]): Promise<void> {
159170
let bufferEntries: KeyValueDBTuple[]
160171

161-
if (!this.cfg.hooks?.mapValueToBuffer) {
172+
if (!this.cfg.hooks.mapValueToBuffer) {
162173
bufferEntries = entries as any
163174
} else {
164175
bufferEntries = await pMap(entries, async ([id, v]) => [
165176
id,
166-
await this.cfg.hooks!.mapValueToBuffer!(v),
177+
await this.cfg.hooks.mapValueToBuffer!(v),
167178
])
168179
}
169180

@@ -187,37 +198,45 @@ export class CommonKeyValueDao<T> {
187198
}
188199

189200
streamValues(limit?: number): ReadableTyped<T> {
190-
if (!this.cfg.hooks?.mapBufferToValue) {
201+
const { mapBufferToValue } = this.cfg.hooks
202+
203+
if (!mapBufferToValue) {
191204
return this.cfg.db.streamValues(this.cfg.table, limit)
192205
}
193206

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))
196207
const stream: ReadableTyped<T> = this.cfg.db
197208
.streamValues(this.cfg.table, limit)
198209
.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-
)
210+
.flatMap(async (buf: Buffer) => {
211+
try {
212+
return [await mapBufferToValue(buf)] satisfies T[]
213+
} catch (err) {
214+
this.cfg.logger.error(err)
215+
return [] // SKIP
216+
}
217+
})
204218

205219
return stream
206220
}
207221

208222
streamEntries(limit?: number): ReadableTyped<KeyValueTuple<string, T>> {
209-
if (!this.cfg.hooks?.mapBufferToValue) {
223+
const { mapBufferToValue } = this.cfg.hooks
224+
225+
if (!mapBufferToValue) {
210226
return this.cfg.db.streamEntries(this.cfg.table, limit)
211227
}
212228

213229
const stream: ReadableTyped<KeyValueTuple<string, T>> = this.cfg.db
214230
.streamEntries(this.cfg.table, limit)
215231
.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-
)
232+
.flatMap(async ([id, buf]: KeyValueTuple<string, Buffer>) => {
233+
try {
234+
return [[id, await mapBufferToValue(buf)]] satisfies KeyValueTuple<string, T>[]
235+
} catch (err) {
236+
this.cfg.logger.error(err)
237+
return [] // SKIP
238+
}
239+
})
221240

222241
return stream
223242
}

src/testing/daoTest.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Readable } from 'node:stream'
22
import { _deepCopy, _pick, _sortBy, _omit, localTimeNow } from '@naturalcycles/js-lib'
3-
import { _pipeline, readableToArray, transformNoOp } from '@naturalcycles/nodejs-lib'
3+
import { _pipeline, readableToArray } from '@naturalcycles/nodejs-lib'
44
import { CommonDaoLogLevel, DBQuery } from '..'
55
import { CommonDB } from '../common.db'
66
import { CommonDao } from '../commondao/common.dao'
@@ -194,11 +194,7 @@ export function runCommonDaoTest(db: CommonDB, quirks: CommonDBImplementationQui
194194
})
195195

196196
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()
202198

203199
rows = _sortBy(rows, r => r.id)
204200
expectMatch(expectedItems, rows, quirks)

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -879,9 +879,9 @@
879879
zod "^3.20.2"
880880

881881
"@naturalcycles/nodejs-lib@^13.0.1", "@naturalcycles/nodejs-lib@^13.0.2", "@naturalcycles/nodejs-lib@^13.1.1":
882-
version "13.9.1"
883-
resolved "https://registry.yarnpkg.com/@naturalcycles/nodejs-lib/-/nodejs-lib-13.9.1.tgz#98aa9ee69cdbe12c20793f2d7b3375556e7d8269"
884-
integrity sha512-IpUGFlepG1KsOi5srYM3zHvlQt4ZXyNeoGd22ggbM2Gi8prJOxbFC76cNwe2Qj9PS+5KHSayGbAhxnpRTREyVg==
882+
version "13.10.0"
883+
resolved "https://registry.yarnpkg.com/@naturalcycles/nodejs-lib/-/nodejs-lib-13.10.0.tgz#0914dea4fced163a9642f09def35f242b9daf565"
884+
integrity sha512-KKxpAb6oK250Lk7t2nGTrc+T3YD6R0Ba0c19wbuJNXDZToZ0FRooQc1vgcrjksMpHDebWBPYz0g9IIIbA+gQzA==
885885
dependencies:
886886
"@naturalcycles/js-lib" "^14.0.0"
887887
"@types/js-yaml" "^4.0.9"

0 commit comments

Comments
 (0)