UNPKG

23.9 kBMarkdownView Raw
1# streaming-iterables 🏄‍♂️
2
3[![Node CI](https://github.com/reconbot/streaming-iterables/workflows/Node%20CI/badge.svg?branch=master)](https://github.com/reconbot/streaming-iterables/actions?query=workflow%3A%22Node+CI%22) [![Try streaming-iterables on RunKit](https://badge.runkitcdn.com/streaming-iterables.svg)](https://npm.runkit.com/streaming-iterables) [![install size](https://packagephobia.now.sh/badge?p=streaming-iterables)](https://packagephobia.now.sh/result?p=streaming-iterables)
4
5A Swiss army knife for [async iterables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of). Designed to help replace your streams. These utilities have a comparable speed, friendlier error handling, and are easier to understand than most stream based workloads.
6
7Streams were our last best hope for processing unbounded amounts of data. Since Node 10 they have become something greater, they've become async iterable. With async iterators you can have less code, do more work, faster.
8
9If you still need streams with async functions, check out sister project [`bluestream`🏄‍♀️](https://www.npmjs.com/package/bluestream)!
10
11We support and test against LTS node releases, but may work with older versions of node.
12
13## Install
14
15There are no dependencies.
16
17```bash
18npm install streaming-iterables
19```
20
21We ship esm, umd and types.
22
23## Overview
24
25Every function is curryable, you can call it with any number of arguments. For example:
26
27```ts
28import { map } from 'streaming-iterables'
29
30for await (const str of map(String, [1,2,3])) {
31 console.log(str)
32}
33// "1", "2", "3"
34
35const stringable = map(String)
36for await (const str of stringable([1,2,3])) {
37 console.log(str)
38}
39// "1", "2", "3"
40```
41
42Since this works with async iterators it requires node 10 or higher.
43
44## API
45
46- [`batch()`](#batch)
47- [`batchWithTimeout()`](#batchwithtimeout)
48- [`buffer()`](#buffer)
49- [`collect()`](#collect)
50- [`concat()`](#concat)
51- [`consume()`](#consume)
52- [`drop()`](#drop)
53- [`flatMap()`](#flatmap)
54- [`flatten()`](#flatten)
55- [`flatTransform()`](#flattransform)
56- [`fromStream()`](#fromstream)
57- [`filter()`](#filter)
58- [`getIterator()`](#getiterator)
59- [`map()`](#map)
60- [`merge()`](#merge)
61- [`parallelMap()`](#parallelmap)
62- [`parallelMerge()`](#parallelmerge)
63- [`pipeline()`](#pipeline)
64- [`reduce()`](#reduce)
65- [`take()`](#take)
66- [`takeLast()`](#takelast)
67- [`takeWhile()`](#takewhile)
68- [`tap()`](#tap)
69- [`throttle()`](#throttle)
70- [`time()`](#time)
71- [`transform()`](#transform)
72- [`writeToStream()`](#writetostream)
73
74### batch
75
76```ts
77function batch<T>(size: number, iterable: AsyncIterable<T>): AsyncGenerator<T[]>
78function batch<T>(size: number, iterable: Iterable<T>): Generator<T[]>
79```
80
81Batch objects from `iterable` into arrays of `size` length. The final array may be shorter than size if there is not enough items. Returns a sync iterator if the `iterable` is sync, otherwise an async iterator. Errors from the source `iterable` are immediately raised.
82
83`size` can be between 1 and `Infinity`.
84
85```ts
86import { batch } from 'streaming-iterables'
87import { getPokemon } from 'iterable-pokedex'
88
89// batch 10 pokemon while we process them
90for await (const pokemons of batch(10, getPokemon())) {
91 console.log(pokemons) // 10 pokemon at a time!
92}
93```
94
95### batchWithTimeout
96
97```ts
98function batchWithTimeout<T>(size: number, timeout: number, iterable: AsyncIterable<T>): AsyncGenerator<T[]>
99function batchWithTimeout<T>(size: number, timeout: number, iterable: Iterable<T>): Generator<T[]>
100```
101
102Like [`batch`](#batch) but flushes early if the `timeout` is reached. The batches may be shorter than size if there are not enough items. Returns a sync iterator if the `iterable` is sync, otherwise an async iterator. Errors from the source `iterable` are immediately raised.
103
104`size` can be between 1 and `Infinity`.
105`timeout` can be between 0 and `Infinity`.
106
107```ts
108import { batchWithTimeout } from 'streaming-iterables'
109import { getPokemon } from 'iterable-pokedex'
110
111// batch 10 pokemon while we process them
112for await (const pokemons of batchWithTimeout(10, 100, getPokemon())) {
113 console.log(pokemons) // Up to 10 pokemon at a time!
114}
115```
116
117### buffer
118
119```ts
120function buffer<T>(size: number, iterable: AsyncIterable<T>): AsyncIterable<T>
121function buffer<T>(size: number, iterable: Iterable<T>): AsyncIterable<T>
122```
123
124Buffer keeps a number of objects in reserve available for immediate reading. This is helpful with async iterators as it will pre-fetch results so you don't have to wait for them to load. For sync iterables it will pre-compute up to `size` values and keep them in reserve. The internal buffer will start to be filled once `.next()` is called for the first time and will continue to fill until the source `iterable` is exhausted or the buffer is full. Errors from the source `iterable` will be raised after all buffered values are yielded.
125
126`size` can be between 0 and `Infinity`.
127
128```ts
129import { buffer } from 'streaming-iterables'
130import { getPokemon, trainMonster } from 'iterable-pokedex'
131
132// load 10 monsters in the background while we process them one by one
133for await (const monster of buffer(10, getPokemon())) {
134 await trainMonster(monster) // got to do some pokéwork
135}
136```
137
138### collect
139
140```ts
141function collect<T>(iterable: Iterable<T>): T[]
142function collect<T>(iterable: AsyncIterable<T>): Promise<T[]>
143```
144
145Collect all the values from an iterable into an array. Returns an array if you pass it an iterable and a promise for an array if you pass it an async iterable. Errors from the source `iterable` are raised immediately.
146
147```ts
148import { collect } from 'streaming-iterables'
149import { getPokemon } from 'iterable-pokedex'
150
151console.log(await collect(getPokemon()))
152// [bulbasaur, ivysaur, venusaur, charmander, ...]
153```
154
155### concat
156
157```ts
158function concat(...iterables: Array<Iterable<any>>): Iterable<any>
159function concat(...iterables: Array<AnyIterable<any>>): AsyncIterable<any>
160```
161
162Combine multiple iterators into a single iterable. Reads each iterable completely one at a time. Returns a sync iterator if all `iterables` are sync, otherwise it returns an async iterable. Errors from the source `iterable` are raised immediately.
163
164```ts
165import { concat } from 'streaming-iterables'
166import { getPokemon } from 'iterable-pokedex'
167import { getTransformers } from './util'
168
169for await (const hero of concat(getPokemon(2), getTransformers(2))) {
170 console.log(hero)
171}
172// charmander
173// bulbasaur <- end of pokemon
174// megatron
175// bumblebee <- end of transformers
176```
177
178### consume
179
180```ts
181export function consume<T>(iterable: Iterable<T>): void
182export function consume<T>(iterable: AsyncIterable<T>): Promise<void>
183```
184
185A promise that resolves after the function drains the iterable of all data. Useful for processing a pipeline of data. Errors from the source `iterable` are raised immediately.
186
187```ts
188import { consume, map } from 'streaming-iterables'
189import { getPokemon, trainMonster } from 'iterable-pokedex'
190
191const train = map(trainMonster)
192await consume(train(getPokemon())) // load all the pokemon and train them!
193```
194
195### drop
196
197```ts
198function drop<T>(count: number, iterable: AsyncIterable<T>): AsyncIterableIterator<T>
199function drop<T>(count: number, iterable: Iterable<T>): IterableIterator<T>
200```
201
202Returns a new iterator that skips a specific number of items from `iterable`. When used with generators it advances the generator `count` items, when used with arrays it gets a new iterator and skips `count` items.
203
204```ts
205import { pipeline, drop, collect } from 'streaming-iterables'
206import { getPokemon } from 'iterable-pokedex'
207
208const allButFirstFive = await collect(drop(5, getPokemon()))
209// first five pokemon
210```
211
212### flatMap
213
214```ts
215function flatMap<T, B>(func: (data: T) => FlatMapValue<B>, iterable: AnyIterable<T>): AsyncGenerator<B>
216```
217
218Map `func` over the `iterable`, flatten the result and then ignore all null or undefined values. It's the transform function we've always needed. It's equivalent to;
219
220```ts
221(func, iterable) => filter(i => i !== undefined && i !== null, flatten(map(func, iterable)))
222```
223
224*note*: The return value for `func` is `FlatMapValue<B>`. Typescript doesn't have recursive types but you can nest iterables as deep as you like.
225
226The ordering of the results is guaranteed. Errors from the source `iterable` are raised after all mapped values are yielded. Errors from `func` are raised after all previously mapped values are yielded.
227
228```ts
229import { flatMap } from 'streaming-iterables'
230import { getPokemon, lookupStats } from 'iterable-pokedex'
231
232async function getDefeatedGyms(pokemon) {
233 if (pokemon.gymBattlesWon > 0) {
234 const stats = await lookupStats(pokemon)
235 return stats.gyms
236 }
237}
238
239for await (const gym of flatMap(getDefeatedGyms, getPokemon())) {
240 console.log(gym.name)
241}
242// "Pewter Gym"
243// "Cerulean Gym"
244// "Vermilion Gym"
245```
246
247### flatten
248
249```ts
250function flatten<B>(iterable: AnyIterable<B | AnyIterable<B>>): AsyncIterableIterator<B>
251```
252
253Returns a new iterator by pulling every item out of `iterable` (and all its sub iterables) and yielding them depth-first. Checks for the iterable interfaces and iterates it if it exists. If the value is a string it is not iterated as that ends up in an infinite loop. Errors from the source `iterable` are raised immediately.
254
255*note*: Typescript doesn't have recursive types but you can nest iterables as deep as you like.
256
257```ts
258import { flatten } from 'streaming-iterables'
259
260for await (const item of flatten([1, 2, [3, [4, 5], 6])) {
261 console.log(item)
262}
263// 1
264// 2
265// 3
266// 4
267// 5
268// 6
269```
270
271### flatTransform
272
273```ts
274function flatTransform<T, R>(concurrency: number, func: (data: T) => FlatMapValue<R>, iterable: AnyIterable<T>): AsyncIterableIterator<R>
275```
276
277Map `func` over the `iterable`, flatten the result and then ignore all null or undefined values. Returned async iterables are flattened concurrently too. It's the transform function we've always wanted.
278
279It's similar to;
280
281```ts
282const filterEmpty = filter(i => i !== undefined && i !== null)
283(concurrency, func, iterable) => filterEmpty(flatten(transform(concurrency, func, iterable)))
284```
285
286*note*: The return value for `func` is `FlatMapValue<B>`. Typescript doesn't have recursive types but you can nest iterables as deep as you like. However only directly returned async iterables are processed concurrently. (Eg, if you use an async generator function as `func` it's output will be processed concurrently, but if it's nested inside other iterables it will be processed sequentially.)
287
288Order is determined by when async operations resolve. And it will run up to `concurrency` async operations at once. This includes promises and async iterables returned from `func`. Errors from the source `iterable` are raised after all transformed values are yielded. Errors from `func` are raised after all previously transformed values are yielded.
289
290`concurrency` can be between 1 and `Infinity`.
291
292Promise Example;
293
294```ts
295import { flatTransform } from 'streaming-iterables'
296import { getPokemon, lookupStats } from 'iterable-pokedex'
297
298async function getDefeatedGyms(pokemon) {
299 if (pokemon.gymBattlesWon > 0) {
300 const stats = await lookupStats(pokemon)
301 return stats.gyms
302 }
303}
304
305// lookup 10 stats at a time
306for await (const gym of flatTransform(10, getDefeatedGyms, getPokemon())) {
307 console.log(gym.name)
308}
309// "Pewter Gym"
310// "Cerulean Gym"
311// "Vermilion Gym"
312```
313
314Async Generator Example
315
316```ts
317import { flatTransform } from 'streaming-iterables'
318import { getPokemon } from 'iterable-pokedex'
319import { findFriendsFB, findFriendsMySpace } from './util'
320
321
322async function* findFriends (pokemon) {
323 yield await findFriendsFB(pokemon.name)
324 yield await findFriendsMySpace(pokemon.name)
325}
326
327for await (const pokemon of flatTransform(10, findFriends, getPokemon())) {
328 console.log(pokemon.name)
329}
330// Pikachu
331// Meowth
332// Ash - FB
333// Jessie - FB
334// Misty - MySpace
335// James - MySpace
336```
337
338### fromStream
339
340```ts
341function fromStream<T>(stream: Readable): AsyncIterable<T>
342```
343
344Wraps the stream in an async iterator or returns the stream if it already is an async iterator.
345
346*note*: Since Node 10, streams already async iterators. This function may be used to ensure compatibility with older versions of Node.
347*note*: This method is deprecated since, node 10 is out of LTS. It may be removed in an upcoming major release.
348
349```ts
350import { fromStream } from 'streaming-iterables'
351import { createReadStream } from 'fs'
352
353const pokeLog = fromStream(createReadStream('./pokedex-operating-system.log'))
354
355for await (const pokeData of pokeLog) {
356 console.log(pokeData) // Buffer(...)
357}
358```
359
360### filter
361
362```ts
363function filter<T>(filterFunc: (data: T) => boolean | Promise<boolean>, iterable: AnyIterable<T>): AsyncIterableIterator<T>
364```
365
366Takes a `filterFunc` and a `iterable`, and returns a new async iterator of the same type containing the members of the given iterable which cause the `filterFunc` to return true.
367
368```ts
369import { filter } from 'streaming-iterables'
370import { getPokemon } from 'iterable-pokedex'
371
372const filterWater = filter(pokemon => pokemon.types.include('Water'))
373
374for await (const pokemon of filterWater(getPokemon())) {
375 console.log(pokemon)
376}
377// squirtle
378// vaporeon
379// magikarp
380```
381
382### getIterator
383
384```ts
385function getIterator<T>(values: Iterableish<T>): Iterator<T> | AsyncIterator<T>
386```
387
388Get the iterator from any iterable or just return an iterator itself.
389
390### map
391
392```ts
393function map<T, B>(func: (data: T) => B | Promise<B>, iterable: AnyIterable<T>): AsyncIterableIterator<B>
394```
395
396Map a function or async function over all the values of an iterable. Errors from the source `iterable` and `func` are raised immediately.
397
398```ts
399import { consume, map } from 'streaming-iterables'
400import got from 'got'
401
402const urls = ['https://http.cat/200', 'https://http.cat/201', 'https://http.cat/202']
403const download = map(got)
404
405// download one at a time
406for await (page of download(urls)) {
407 console.log(page)
408}
409```
410
411### merge
412
413```ts
414function merge(...iterables: Array<AnyIterable<any>>): AsyncIterableIterator<any>
415```
416
417Combine multiple iterators into a single iterable. Reads one item off each iterable in order repeatedly until they are all exhausted. If you care less about order and want them faster see [`parallelMerge()`](#parallelmerge).
418
419### parallelMap
420
421```ts
422function parallelMap<T, R>(concurrency: number, func: (data: T) => R | Promise<R>, iterable: AnyIterable<T>): AsyncIterableIterator<R>
423```
424
425Map a function or async function over all the values of an iterable and do them concurrently. Errors from the source `iterable` are raised after all mapped values are yielded. Errors from `func` are raised after all previously mapped values are yielded. Just like [`map()`](#map).
426
427`concurrency` can be between 1 and `Infinity`.
428
429If you don't care about order, see the faster [`transform()`](#transform) function.
430
431```ts
432import { consume, parallelMap } from 'streaming-iterables'
433import got from 'got'
434
435const urls = ['https://http.cat/200', 'https://http.cat/201', 'https://http.cat/202']
436const download = parallelMap(2, got)
437
438// download two at a time
439for await (page of download(urls)) {
440 console.log(page)
441}
442```
443
444### parallelMerge
445
446```ts
447function parallelMerge<T>(...iterables: Array<AnyIterable<T>>): AsyncIterableIterator<T>
448```
449
450Combine multiple iterators into a single iterable. Reads one item off of every iterable and yields them as they resolve. This is useful for pulling items out of a collection of iterables as soon as they're available. Errors `iterables` are raised immediately.
451
452```ts
453import { parallelMerge } from 'streaming-iterables'
454import { getPokemon, getTransformer } from 'iterable-pokedex'
455
456// pokemon are much faster to load btw
457const heros = parallelMerge(getPokemon(), getTransformer())
458for await (const hero of heros) {
459 console.log(hero)
460}
461// charmander
462// bulbasaur
463// megatron
464// pikachu
465// eevee
466// bumblebee
467// jazz
468```
469
470### pipeline
471
472```ts
473function pipeline(firstFn: Function, ...fns: Function[]): any;
474```
475
476Calls `firstFn` and then every function in `fns` with the result of the previous function. The final return is the result of the last function in `fns`.
477
478```ts
479import { pipeline, map, collect } from 'streaming-iterables'
480import { getPokemon } from 'iterable-pokedex'
481const getName = map(pokemon => pokemon.name)
482
483// equivalent to `await collect(getName(getPokemon()))`
484await pipeline(getPokemon, getName, collect)
485// charmander
486// bulbasaur
487// MissingNo.
488```
489
490### reduce
491
492```ts
493function reduce<T, B>(func: (acc: B, value: T) => B, start: B, iterable: AnyIterable<T>): Promise<B>
494```
495
496An async function that takes a reducer function, an initial value and an iterable.
497
498Reduces an iterable to a value which is the accumulated result of running each value from the iterable thru `func`, where each successive invocation is supplied the return value of the previous. Errors are immediate raised.
499
500### take
501
502```ts
503function take<T>(count: number, iterable: AsyncIterable<T>): AsyncIterableIterator<T>
504function take<T>(count: number, iterable: Iterable<T>): IterableIterator<T>
505```
506
507Returns a new iterator that reads a specific number of items from `iterable`. When used with generators it advances the generator, when used with arrays it gets a new iterator and starts from the beginning.
508
509```ts
510import { pipeline, take, collect } from 'streaming-iterables'
511import { getPokemon } from 'iterable-pokedex'
512
513const topFive = await collect(take(5, getPokemon()))
514// first five pokemon
515```
516
517### takeLast
518
519```ts
520function takeLast<T>(count: number, iterable: AsyncIterable<T>): AsyncIterableIterator<T>
521function takeLast<T>(count: number, iterable: Iterable<T>): IterableIterator<T>
522```
523
524Returns a new iterator that reads a specific number of items from the end of `iterable` once it has completed. When used with generators it advances the generator, when used with arrays it gets a new iterator and starts from the beginning.
525
526```ts
527import { pipeline, takeLast, collect } from 'streaming-iterables'
528import { getPokemon } from 'iterable-pokedex'
529
530const bottomFive = await collect(takeLast(5, getPokemon()))
531// last five pokemon
532```
533
534### takeWhile
535
536```ts
537function takeWhile<T, S extends T>(predicate: (data: T) => data is S, iterable: AnyIterable<T>): AsyncGenerator<S>;
538```
539
540Takes a `predicate` and a `iterable`, and returns a new async iterator of the same type containing the members of the given iterable until the `predicate` returns false.
541
542```ts
543import { takeWhile } from 'streaming-iterables'
544import { getPokemon } from 'iterable-pokedex'
545
546const firstSlowOnes = takeWhile(pokemon => pokemon.baseStats.speed < 100)
547
548for await (const pokemon of firstSlowOnes(getPokemon())) {
549 console.log(pokemon)
550}
551// Abomasnow
552// Abra
553// Absol
554```
555
556### tap
557
558```ts
559function tap<T>(func: (data: T) => any, iterable: AnyIterable<T>): AsyncIterableIterator<T>
560```
561
562Returns a new iterator that yields the data it consumes, passing the data through to a function. If you provide an async function, the iterator will wait for the promise to resolve before yielding the value. This is useful for logging, or processing information and passing it along.
563
564### throttle
565
566```ts
567function throttle<T>(limit: number, interval: number, iterable: AnyIterable<T>): AsyncGenerator<T>
568```
569
570Throttles `iterable` at a rate of `limit` per `interval` without discarding data. Useful for throttling rate limited APIs.
571
572`limit` can be greater than 0 but less than `Infinity`.
573`interval` can be greater than or equal to 0 but less than `Infinity`.
574
575```ts
576import { throttle } from 'streaming-iterables'
577import { getPokemon, trainMonster } from 'iterable-pokedex'
578
579// load monsters at a maximum rate of 1 per second
580for await (const monster of throttle(1, 1000, getPokemon())) {
581 await trainMonster(monster)
582}
583```
584
585### time
586
587```ts
588function time<T>(config?: ITimeConfig, iterable: AsyncIterable<R>): AsyncIterableIterator<R>
589function time<T>(config?: ITimeConfig, iterable: Iterable<R>): IterableIterator<R>
590
591interface ITimeConfig {
592 progress?: (delta: [number, number], total: [number, number]) => any;
593 total?: (time: [number, number]) => any;
594}
595```
596
597Returns a new iterator that yields the data it consumes and calls the `progress` and `total` callbacks with the [`hrtime`](https://nodejs.org/api/process.html#process_process_hrtime_time) it took for `iterable` to provide a value when `.next()` was called on it. That is to say, the time returned is the time this iterator spent waiting for data, not the time it took to finish being read. The `hrtime` tuple looks like `[seconds, nanoseconds]`.
598
599```ts
600import { consume, transform, time } from 'streaming-iterables'
601import got from 'got'
602
603const urls = ['https://http.cat/200', 'https://http.cat/201', 'https://http.cat/202']
604const download = transform(1000, got)
605const timer = time({
606 total: total => console.log(`Spent ${total[0]} seconds and ${total[1]}ns downloading cats`),
607})
608// download all of these at the same time
609for await (page of timer(download(urls))) {
610 console.log(page)
611}
612```
613
614### transform
615
616```ts
617function transform<T, R>(concurrency: number, func: (data: T) => R | Promise<R>, iterable: AnyIterable<T>): AsyncIterableIterator<R>
618```
619
620Map a function or async function over all the values of an iterable. Order is determined by when `func` resolves. And it will run up to `concurrency` async `func` operations at once. If you care about order see [`parallelMap()`](#parallelmap). Errors from the source `iterable` are raised after all transformed values are yielded. Errors from `func` are raised after all previously transformed values are yielded.
621
622`concurrency` can be between 1 and `Infinity`.
623
624```ts
625import { consume, transform } from 'streaming-iterables'
626import got from 'got'
627
628const urls = ['https://http.cat/200', 'https://http.cat/201', 'https://http.cat/202']
629const download = transform(1000, got)
630
631// download all of these at the same time
632for await (page of download(urls)) {
633 console.log(page)
634}
635```
636
637### writeToStream
638
639```ts
640function writeToStream(stream: Writable, iterable: AnyIterable<any>): Promise<void>
641```
642
643Writes the `iterable` to the stream respecting the stream back pressure. Resolves when the iterable is exhausted, rejects if the stream errors during calls to `write()` or if there are `error` events during the write.
644
645As it is when working with streams there are a few caveats;
646
647- It is possible for the stream to error after `writeToStream()` has finished writing due to internal buffering and other concerns, so always handle errors on the stream as well.
648- `writeToStream()` doesn't close the stream like `stream.pipe()` might. This is done so you can write to the stream multiple times. You can call `stream.write(null)` or any stream specific end function if you are done with the stream.
649
650```ts
651import { pipeline, map, writeToStream } from 'streaming-iterables'
652import { getPokemon } from 'iterable-pokedex'
653import { createWriteStream } from 'fs'
654
655const file = createWriteStream('pokemon.ndjson')
656const serialize = map(pokemon => `${JSON.stringify(pokemon)}\n`)
657await pipeline(getPokemon, serialize, writeToStream(file))
658file.end() // close the stream
659// now all the pokemon are written to the file!
660```
661
662## Types
663
664### Iterableish
665
666```ts
667type Iterableish<T> = Iterable<T> | Iterator<T> | AsyncIterable<T> | AsyncIterator<T>
668```
669
670Any iterable or iterator.
671
672### AnyIterable
673
674```ts
675type AnyIterable<T> = Iterable<T> | AsyncIterable<T>
676```
677
678Literally any `Iterable` (async or regular).
679
680### FlatMapValue
681
682```ts
683type FlatMapValue<B> = B | AnyIterable<B> | undefined | null | Promise<B | AnyIterable<B> | undefined | null>
684```
685
686A value, an array of that value, undefined, null or promises for any of them. Used in the `flatMap` and `flatTransform` functions as possible return values of the mapping function.
687
688## Contributors wanted
689
690Writing docs and code is a lot of work! Thank you in advance for helping out.