@naturalcycles/js-lib 14.74.0 → 14.77.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.
package/src/seq/seq.ts CHANGED
@@ -1,4 +1,11 @@
1
- import { AbortableMapper, AbortablePredicate, END, SKIP } from '../types'
1
+ import {
2
+ AbortableAsyncMapper,
3
+ AbortableAsyncPredicate,
4
+ AbortableMapper,
5
+ AbortablePredicate,
6
+ END,
7
+ SKIP,
8
+ } from '../types'
2
9
 
3
10
  /**
4
11
  * Inspired by Kotlin Sequences.
@@ -8,7 +15,7 @@ import { AbortableMapper, AbortablePredicate, END, SKIP } from '../types'
8
15
  *
9
16
  * @experimental
10
17
  */
11
- export class Seq<T> implements Iterable<T> {
18
+ export class Sequence<T> implements Iterable<T> {
12
19
  private constructor(initialValue: T | typeof END, private nextFn: AbortableMapper<T, T>) {
13
20
  this.currentValue = initialValue
14
21
  }
@@ -22,29 +29,29 @@ export class Seq<T> implements Iterable<T> {
22
29
  }
23
30
  }
24
31
 
25
- static create<T>(initialValue: T | typeof END, nextFn: AbortableMapper<T, T>): Seq<T> {
26
- return new Seq(initialValue, nextFn)
32
+ static create<T>(initialValue: T | typeof END, nextFn: AbortableMapper<T, T>): Sequence<T> {
33
+ return new Sequence(initialValue, nextFn)
27
34
  }
28
35
 
29
- static range(minIncl: number, maxExcl: number, step = 1): Seq<number> {
36
+ static range(minIncl: number, maxExcl: number, step = 1): Sequence<number> {
30
37
  const max = maxExcl - step
31
- return new Seq(minIncl, n => (n < max ? n + step : END))
38
+ return new Sequence(minIncl, n => (n < max ? n + step : END))
32
39
  }
33
40
 
34
- static from<T>(a: Iterable<T>): Seq<T> {
41
+ static from<T>(a: Iterable<T>): Sequence<T> {
35
42
  const it = a[Symbol.iterator]()
36
43
  const v = it.next()
37
- if (v.done) return new Seq<any>(END, () => {})
44
+ if (v.done) return new Sequence<any>(END, () => {})
38
45
 
39
- return new Seq(v.value, () => {
46
+ return new Sequence(v.value, () => {
40
47
  const v = it.next()
41
48
  if (v.done) return END
42
49
  return v.value
43
50
  })
44
51
  }
45
52
 
46
- static empty<T = any>(): Seq<T> {
47
- return new Seq(END as any, () => {})
53
+ static empty<T = any>(): Sequence<T> {
54
+ return new Sequence(END as any, () => {})
48
55
  }
49
56
 
50
57
  private currentValue: T | typeof END
@@ -133,11 +140,135 @@ export class Seq<T> implements Iterable<T> {
133
140
  a.push(v)
134
141
  }
135
142
  }
143
+
144
+ forEach(fn: (v: T, i: number) => void): void {
145
+ let i = -1
146
+ // eslint-disable-next-line no-constant-condition
147
+ while (true) {
148
+ const v = this.next()
149
+ if (v === END) return
150
+ fn(v, ++i)
151
+ }
152
+ }
136
153
  }
137
154
 
138
155
  /**
139
156
  * Convenience function to create a Sequence.
140
157
  */
141
- export function _seq<T>(initialValue: T | typeof END, nextFn: AbortableMapper<T, T>): Seq<T> {
142
- return Seq.create(initialValue, nextFn)
158
+ export function _seq<T>(initialValue: T | typeof END, nextFn: AbortableMapper<T, T>): Sequence<T> {
159
+ return Sequence.create(initialValue, nextFn)
160
+ }
161
+
162
+ /* eslint-disable no-await-in-loop */
163
+
164
+ /**
165
+ * Experimental.
166
+ * Feasibility to be proven.
167
+ *
168
+ * @experimental
169
+ */
170
+ export class AsyncSequence<T> implements AsyncIterable<T> {
171
+ private constructor(initialValue: T | typeof END, private nextFn: AbortableAsyncMapper<T, T>) {
172
+ this.currentValue = initialValue
173
+ }
174
+
175
+ [Symbol.asyncIterator](): AsyncIterator<T> {
176
+ return {
177
+ next: async () => {
178
+ const value = await this.next()
179
+ return value === END ? { done: true, value: undefined } : { value }
180
+ },
181
+ }
182
+ }
183
+
184
+ static create<T>(
185
+ initialValue: T | typeof END,
186
+ nextFn: AbortableAsyncMapper<T, T>,
187
+ ): AsyncSequence<T> {
188
+ return new AsyncSequence(initialValue, nextFn)
189
+ }
190
+
191
+ static async from<T>(a: AsyncIterable<T>): Promise<AsyncSequence<T>> {
192
+ const it = a[Symbol.asyncIterator]()
193
+ const v = await it.next()
194
+ if (v.done) return new AsyncSequence<any>(END, () => {})
195
+
196
+ return new AsyncSequence(v.value, async () => {
197
+ const v = await it.next()
198
+ if (v.done) return END
199
+ return v.value
200
+ })
201
+ }
202
+
203
+ static empty<T = any>(): AsyncSequence<T> {
204
+ return new AsyncSequence(END as any, () => {})
205
+ }
206
+
207
+ private currentValue: T | typeof END
208
+ private sentInitialValue = false
209
+ private i = -1
210
+
211
+ async next(): Promise<T | typeof END> {
212
+ if (this.currentValue === END) return END
213
+
214
+ this.i++
215
+
216
+ let v: T | typeof SKIP | typeof END
217
+
218
+ if (!this.sentInitialValue) {
219
+ this.sentInitialValue = true
220
+ v = this.currentValue
221
+ } else {
222
+ v = await this.nextFn(this.currentValue, this.i)
223
+ }
224
+
225
+ // console.log(`_seq`, v)
226
+
227
+ if (v === SKIP) return await this.next()
228
+
229
+ return (this.currentValue = v)
230
+ }
231
+
232
+ // Final functions - return final value, not a chained sequence
233
+ async find(predicate: AbortableAsyncPredicate<T>): Promise<T | undefined> {
234
+ do {
235
+ const v = await this.next()
236
+ if (v === END) return // not found, end of sequence
237
+ const r = await predicate(v, this.i)
238
+ if (r === END) return
239
+ if (r) return v
240
+ // otherwise proceed
241
+ } while (true) // eslint-disable-line no-constant-condition
242
+ }
243
+
244
+ async some(predicate: AbortableAsyncPredicate<T>): Promise<boolean> {
245
+ do {
246
+ const v = await this.next()
247
+ if (v === END) return false
248
+ const r = await predicate(v, this.i)
249
+ if (r === END) return false
250
+ if (r) return true
251
+ } while (true) // eslint-disable-line no-constant-condition
252
+ }
253
+
254
+ async every(predicate: AbortableAsyncPredicate<T>): Promise<boolean> {
255
+ do {
256
+ const v = await this.next()
257
+ if (v === END) return true
258
+ const r = await predicate(v, this.i)
259
+ if (r === END) return true
260
+ if (!r) return false
261
+ } while (true) // eslint-disable-line no-constant-condition
262
+ }
263
+
264
+ async toArray(): Promise<T[]> {
265
+ const a: T[] = []
266
+
267
+ // eslint-disable-next-line no-constant-condition
268
+ while (true) {
269
+ const v = await this.next()
270
+ if (v === END) return a
271
+ a.push(v)
272
+ }
273
+ }
143
274
  }
@@ -76,16 +76,20 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
76
76
  // Error or ErrorObject
77
77
  //
78
78
 
79
+ // Omit "default" error name as non-informative
80
+ // UPD: no, it's still important to understand that we're dealing with Error and not just some string
81
+ // if (obj?.name === 'Error') {
82
+ // s = obj.message
83
+ // }
84
+ s = [obj.name, obj.message].join(': ')
85
+
79
86
  if (opt.includeErrorStack && obj.stack) {
80
- // Stack includes message
81
- s = obj.stack
82
- } else {
83
- // Omit "default" error name as non-informative
84
- // UPD: no, it's still important to understand that we're dealing with Error and not just some string
85
- // if (obj?.name === 'Error') {
86
- // s = obj.message
87
- // }
88
- s = [obj.name, obj.message].join(': ')
87
+ // Here we're using the previously-generated "title line" (e.g "Error: some_message"),
88
+ // concatenating it with the Stack (but without the title line of the Stack)
89
+ // This is to fix the rare error (happened with Got) where `err.message` was changed,
90
+ // but err.stack had "old" err.message
91
+ // This should "fix" that
92
+ s = [s, ...obj.stack.split('\n').slice(1)].join('\n')
89
93
  }
90
94
 
91
95
  if (_isErrorObject(obj)) {
@@ -1,9 +1,9 @@
1
1
  export function _gb(b: number): number {
2
- return Math.round(b / (1024 * 1024 * 1024))
2
+ return Math.round(b / 1024 ** 3)
3
3
  }
4
4
 
5
5
  export function _mb(b: number): number {
6
- return Math.round(b / (1024 * 1024))
6
+ return Math.round(b / 1024 ** 2)
7
7
  }
8
8
 
9
9
  export function _kb(b: number): number {
@@ -14,8 +14,24 @@ export function _kb(b: number): number {
14
14
  * Byte size to Human byte size string
15
15
  */
16
16
  export function _hb(b = 0): string {
17
- if (b < 800) return `${Math.round(b)} byte(s)`
18
- if (b < 800 * 1024) return `${Math.round(b / 1024)} Kb`
19
- if (b < 800 * 1024 * 1024) return `${Math.round(b / 1024 / 1024)} Mb`
20
- return `${Math.round(b / 1024 / 1024 / 1024)} Gb`
17
+ if (b < 1024) return `${Math.round(b)} byte`
18
+ if (b < 1024 ** 2) return `${(b / 1024).toPrecision(3)} Kb`
19
+ if (b < 1024 ** 3) return `${(b / 1024 ** 2).toPrecision(3)} Mb`
20
+ if (b < 1024 ** 4) return `${(b / 1024 ** 3).toPrecision(3)} Gb`
21
+ if (b < 1024 ** 5) return `${(b / 1024 ** 4).toPrecision(3)} Tb`
22
+ return `${Math.round(b / 1024 ** 4)} Tb`
23
+ }
24
+
25
+ /**
26
+ * hc stands for "human count", similar to "human bytes" `_hb` function.
27
+ * Helpful to print big numbers, as it adds `K` (kilo), `M` (mega), etc to make
28
+ * them more readable.
29
+ */
30
+ export function _hc(c = 0): string {
31
+ if (c < 10 ** 4) return String(c)
32
+ if (c < 10 ** 6) return (c / 10 ** 3).toPrecision(3) + ' K'
33
+ if (c < 10 ** 9) return (c / 10 ** 6).toPrecision(3) + ' M' // million
34
+ if (c < 10 ** 12) return (c / 10 ** 9).toPrecision(3) + ' B' // billion
35
+ if (c < 10 ** 15) return (c / 10 ** 12).toPrecision(3) + ' T' // trillion
36
+ return Math.round(c / 10 ** 12) + ' T'
21
37
  }