@naturalcycles/js-lib 14.86.0 → 14.89.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.
@@ -0,0 +1,362 @@
1
+ import { _assert } from '../error/assert'
2
+ import { IsoDateTime, UnixTimestamp } from '../types'
3
+
4
+ export type LocalTimeUnit = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'
5
+
6
+ export type LocalTimeConfig = LocalTime | Date | IsoDateTime | UnixTimestamp
7
+
8
+ export interface LocalTimeComponents {
9
+ year: number
10
+ month: number
11
+ day: number
12
+ hour: number
13
+ minute: number
14
+ second: number
15
+ }
16
+
17
+ /* eslint-disable no-dupe-class-members */
18
+
19
+ // Design choices:
20
+ // No milliseconds
21
+ // No timezone support, ISO8601 is parsed as LocalDateTime, discarding Timezone information
22
+ // Formats as unix timestamp, ISO8601 or "pretty string"
23
+ // toString and .toJSON formats as unix timestamp
24
+ // No "unixMillis", just pure unixtimestamp
25
+ // .valueOf returns unix timestamp (no millis)
26
+ // Prevents dayjs(undefined) being dayjs.now()
27
+ // Validates on parse, throws if invalid. Doesn't allow invalid objects
28
+ /**
29
+ * @experimental
30
+ */
31
+ export class LocalTime {
32
+ private constructor(private $date: Date) {}
33
+
34
+ /**
35
+ * Parses input String into LocalDate.
36
+ * Input can already be a LocalDate - it is returned as-is in that case.
37
+ */
38
+ static of(d: LocalTimeConfig): LocalTime {
39
+ if (d instanceof LocalTime) return d
40
+ if (d instanceof Date) return new LocalTime(d)
41
+
42
+ if (typeof d === 'number') {
43
+ // unix timestamp
44
+ return new LocalTime(new Date(d * 1000))
45
+ }
46
+
47
+ const date = new Date(d)
48
+
49
+ // validation
50
+ if (isNaN(date.getDate())) {
51
+ throw new TypeError(`Cannot parse "${d}" into LocalTime`)
52
+ }
53
+
54
+ return new LocalTime(date)
55
+ }
56
+
57
+ static unix(ts: UnixTimestamp): LocalTime {
58
+ return new LocalTime(new Date(ts * 1000))
59
+ }
60
+
61
+ static now(): LocalTime {
62
+ return this.of(new Date())
63
+ }
64
+
65
+ static fromComponents(
66
+ c: { year: number; month: number } & Partial<LocalTimeComponents>,
67
+ ): LocalTime {
68
+ return new LocalTime(new Date(c.year, c.month - 1, c.day, c.hour, c.minute, c.second))
69
+ }
70
+
71
+ get(unit: LocalTimeUnit): number {
72
+ if (unit === 'year') {
73
+ return this.$date.getFullYear()
74
+ }
75
+ if (unit === 'month') {
76
+ return this.$date.getMonth() + 1
77
+ }
78
+ if (unit === 'day') {
79
+ return this.$date.getDate()
80
+ }
81
+ if (unit === 'hour') {
82
+ return this.$date.getHours()
83
+ }
84
+ if (unit === 'minute') {
85
+ return this.$date.getMinutes()
86
+ }
87
+ // second
88
+ return this.$date.getSeconds()
89
+ }
90
+
91
+ set(unit: LocalTimeUnit, v: number, mutate = false): LocalTime {
92
+ const t = mutate ? this : this.clone()
93
+
94
+ if (unit === 'year') {
95
+ t.$date.setFullYear(v)
96
+ } else if (unit === 'month') {
97
+ t.$date.setMonth(v - 1)
98
+ } else if (unit === 'day') {
99
+ t.$date.setDate(v)
100
+ } else if (unit === 'hour') {
101
+ t.$date.setHours(v)
102
+ } else if (unit === 'minute') {
103
+ t.$date.setMinutes(v)
104
+ } else if (unit === 'second') {
105
+ t.$date.setSeconds(v)
106
+ }
107
+
108
+ return t
109
+ }
110
+
111
+ year(): number
112
+ year(v: number): LocalTime
113
+ year(v?: number): number | LocalTime {
114
+ return v === undefined ? this.$date.getFullYear() : this.set('year', v)
115
+ }
116
+ month(): number
117
+ month(v: number): LocalTime
118
+ month(v?: number): number | LocalTime {
119
+ return v === undefined ? this.$date.getMonth() + 1 : this.set('month', v)
120
+ }
121
+ date(): number
122
+ date(v: number): LocalTime
123
+ date(v?: number): number | LocalTime {
124
+ return v === undefined ? this.$date.getDate() : this.set('day', v)
125
+ }
126
+ hour(): number
127
+ hour(v: number): LocalTime
128
+ hour(v?: number): number | LocalTime {
129
+ return v === undefined ? this.$date.getHours() : this.set('hour', v)
130
+ }
131
+ minute(): number
132
+ minute(v: number): LocalTime
133
+ minute(v?: number): number | LocalTime {
134
+ return v === undefined ? this.$date.getMinutes() : this.set('minute', v)
135
+ }
136
+ second(): number
137
+ second(v: number): LocalTime
138
+ second(v?: number): number | LocalTime {
139
+ return v === undefined ? this.$date.getSeconds() : this.set('second', v)
140
+ }
141
+
142
+ setComponents(c: Partial<LocalTimeComponents>, mutate = false): LocalTime {
143
+ const d = mutate ? this.$date : new Date(this.$date)
144
+
145
+ if (c.year) {
146
+ d.setFullYear(c.year)
147
+ }
148
+ if (c.month) {
149
+ d.setMonth(c.month - 1)
150
+ }
151
+ if (c.day) {
152
+ d.setDate(c.day)
153
+ }
154
+ if (c.hour !== undefined) {
155
+ d.setHours(c.hour)
156
+ }
157
+ if (c.minute !== undefined) {
158
+ d.setMinutes(c.minute)
159
+ }
160
+ if (c.second !== undefined) {
161
+ d.setSeconds(c.second)
162
+ }
163
+
164
+ return mutate ? this : new LocalTime(d)
165
+ }
166
+
167
+ add(num: number, unit: LocalTimeUnit, mutate = false): LocalTime {
168
+ return this.set(unit, this.get(unit) + num, mutate)
169
+ }
170
+
171
+ subtract(num: number, unit: LocalTimeUnit, mutate = false): LocalTime {
172
+ return this.add(-num, unit, mutate)
173
+ }
174
+
175
+ absDiff(other: LocalTimeConfig, unit: LocalTimeUnit): number {
176
+ return Math.abs(this.diff(other, unit))
177
+ }
178
+
179
+ diff(other: LocalTimeConfig, unit: LocalTimeUnit): number {
180
+ const date2 = LocalTime.of(other).$date
181
+
182
+ if (unit === 'year') {
183
+ return this.$date.getFullYear() - date2.getFullYear()
184
+ }
185
+ if (unit === 'month') {
186
+ return (
187
+ (this.$date.getFullYear() - date2.getFullYear()) * 12 +
188
+ this.$date.getMonth() -
189
+ date2.getMonth()
190
+ )
191
+ }
192
+
193
+ const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000
194
+ let r
195
+
196
+ if (unit === 'day') {
197
+ r = secDiff / (24 * 60 * 60)
198
+ } else if (unit === 'hour') {
199
+ r = secDiff / (60 * 60)
200
+ } else if (unit === 'minute') {
201
+ r = secDiff / 60
202
+ } else {
203
+ // unit === 'second'
204
+ r = secDiff
205
+ }
206
+
207
+ r = r < 0 ? -Math.floor(-r) : Math.floor(r)
208
+ if (Object.is(r, -0)) return 0
209
+ return r
210
+ }
211
+
212
+ startOf(unit: LocalTimeUnit, mutate = false): LocalTime {
213
+ if (unit === 'second') return this
214
+
215
+ if (mutate) {
216
+ const d = this.$date
217
+ d.setSeconds(0)
218
+ if (unit === 'minute') return this
219
+ d.setMinutes(0)
220
+ if (unit === 'hour') return this
221
+ d.setHours(0)
222
+ if (unit === 'day') return this
223
+ d.setDate(0)
224
+ if (unit === 'month') return this
225
+ d.setMonth(0)
226
+ return this
227
+ }
228
+
229
+ const c = this.components()
230
+
231
+ c.second = 0
232
+ if (unit === 'year') {
233
+ c.month = c.day = 1
234
+ c.hour = c.minute = 0
235
+ } else if (unit === 'month') {
236
+ c.day = 1
237
+ c.hour = c.minute = 0
238
+ } else if (unit === 'day') {
239
+ c.hour = c.minute = 0
240
+ } else if (unit === 'hour') {
241
+ c.minute = 0
242
+ }
243
+
244
+ return LocalTime.fromComponents(c)
245
+ }
246
+
247
+ static sort(items: LocalTime[], mutate = false, descending = false): LocalTime[] {
248
+ const mod = descending ? -1 : 1
249
+ return (mutate ? items : [...items]).sort((a, b) => {
250
+ const v1 = a.$date.valueOf()
251
+ const v2 = b.$date.valueOf()
252
+ if (v1 === v2) return 0
253
+ return (v1 < v2 ? -1 : 1) * mod
254
+ })
255
+ }
256
+
257
+ static earliestOrUndefined(items: LocalTime[]): LocalTime | undefined {
258
+ return items.length ? LocalTime.earliest(items) : undefined
259
+ }
260
+
261
+ static earliest(items: LocalTime[]): LocalTime {
262
+ _assert(items.length, 'LocalTime.earliest called on empty array')
263
+
264
+ return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
265
+ }
266
+
267
+ static latestOrUndefined(items: LocalTime[]): LocalTime | undefined {
268
+ return items.length ? LocalTime.latest(items) : undefined
269
+ }
270
+
271
+ static latest(items: LocalTime[]): LocalTime {
272
+ _assert(items.length, 'LocalTime.latest called on empty array')
273
+
274
+ return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
275
+ }
276
+
277
+ isSame(d: LocalTimeConfig): boolean {
278
+ return this.cmp(d) === 0
279
+ }
280
+
281
+ isBefore(d: LocalTimeConfig): boolean {
282
+ return this.cmp(d) === -1
283
+ }
284
+
285
+ isSameOrBefore(d: LocalTimeConfig): boolean {
286
+ return this.cmp(d) <= 0
287
+ }
288
+
289
+ isAfter(d: LocalTimeConfig): boolean {
290
+ return this.cmp(d) === 1
291
+ }
292
+
293
+ isSameOrAfter(d: LocalTimeConfig): boolean {
294
+ return this.cmp(d) >= 0
295
+ }
296
+
297
+ /**
298
+ * Returns 1 if this > d
299
+ * returns 0 if they are equal
300
+ * returns -1 if this < d
301
+ */
302
+ cmp(d: LocalTimeConfig): -1 | 0 | 1 {
303
+ const t1 = this.$date.valueOf()
304
+ const t2 = LocalTime.of(d).$date.valueOf()
305
+ if (t1 === t2) return 0
306
+ return t1 < t2 ? -1 : 1
307
+ }
308
+
309
+ // todo: endOf
310
+
311
+ components(): LocalTimeComponents {
312
+ return {
313
+ year: this.$date.getFullYear(),
314
+ month: this.$date.getMonth() + 1,
315
+ day: this.$date.getDate(),
316
+ hour: this.$date.getHours(),
317
+ minute: this.$date.getMinutes(),
318
+ second: this.$date.getSeconds(),
319
+ }
320
+ }
321
+
322
+ getDate(): Date {
323
+ return this.$date
324
+ }
325
+
326
+ clone(): LocalTime {
327
+ return new LocalTime(new Date(this.$date))
328
+ }
329
+
330
+ unix(): UnixTimestamp {
331
+ return Math.floor(this.$date.valueOf() / 1000)
332
+ }
333
+
334
+ valueOf(): UnixTimestamp {
335
+ return Math.floor(this.$date.valueOf() / 1000)
336
+ }
337
+
338
+ toISO8601(): IsoDateTime {
339
+ return this.$date.toISOString().slice(0, 19)
340
+ }
341
+
342
+ toPretty(): IsoDateTime {
343
+ return this.$date.toISOString().slice(0, 19).split('T').join(' ')
344
+ }
345
+
346
+ toString(): string {
347
+ return String(this.unix())
348
+ }
349
+
350
+ toJSON(): UnixTimestamp {
351
+ return this.unix()
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Shortcut wrapper around `LocalDate.parse` / `LocalDate.today`
357
+ */
358
+ export function localTime(d?: LocalTimeConfig): LocalTime {
359
+ return d ? LocalTime.of(d) : LocalTime.now()
360
+ }
361
+
362
+ // todo: range
@@ -5,9 +5,9 @@ export interface PromiseDecoratorCfg<RES = any, PARAMS = any> {
5
5
 
6
6
  /**
7
7
  * Called BEFORE the original function.
8
- * Returns void.
8
+ * If Promise is returned - it will be awaited.
9
9
  */
10
- beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void
10
+ beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void | Promise<void>
11
11
 
12
12
  /**
13
13
  * Called just AFTER the original function.
@@ -73,77 +73,71 @@ export function _createPromiseDecorator<RES = any, PARAMS = any>(
73
73
  // console.log(`@${cfg.decoratorName} called inside function`)
74
74
  const started = Date.now()
75
75
 
76
- return (
77
- Promise.resolve()
78
- // Before function
79
- .then(() => {
80
- // console.log(`@${cfg.decoratorName} Before`)
81
- if (cfg.beforeFn) {
82
- return cfg.beforeFn({
83
- decoratorParams,
84
- args,
85
- key,
86
- target,
87
- decoratorName,
88
- started,
89
- })
90
- }
76
+ try {
77
+ // Before function
78
+ // console.log(`@${cfg.decoratorName} Before`)
79
+ if (cfg.beforeFn) {
80
+ await cfg.beforeFn({
81
+ decoratorParams,
82
+ args,
83
+ key,
84
+ target,
85
+ decoratorName,
86
+ started,
91
87
  })
92
- // Original function
93
- .then(() => originalMethod.apply(this, args))
94
- .then(res => {
95
- // console.log(`${cfg.decoratorName} After`)
96
- const resp: PromiseDecoratorResp<PARAMS> = {
97
- decoratorParams,
98
- args,
99
- key,
100
- target,
101
- decoratorName,
102
- started,
103
- }
104
-
105
- if (cfg.thenFn) {
106
- res = cfg.thenFn({
107
- ...resp,
108
- res,
109
- })
110
- }
111
-
112
- cfg.finallyFn?.(resp)
113
-
114
- return res
88
+ }
89
+
90
+ // Original function
91
+ let res = await originalMethod.apply(this, args)
92
+
93
+ // console.log(`${cfg.decoratorName} After`)
94
+ const resp: PromiseDecoratorResp<PARAMS> = {
95
+ decoratorParams,
96
+ args,
97
+ key,
98
+ target,
99
+ decoratorName,
100
+ started,
101
+ }
102
+
103
+ if (cfg.thenFn) {
104
+ res = cfg.thenFn({
105
+ ...resp,
106
+ res,
115
107
  })
116
- .catch(err => {
117
- console.error(`@${decoratorName} ${methodSignature} catch:`, err)
118
-
119
- const resp: PromiseDecoratorResp<PARAMS> = {
120
- decoratorParams,
121
- args,
122
- key,
123
- target,
124
- decoratorName,
125
- started,
126
- }
127
-
128
- let handled = false
129
-
130
- if (cfg.catchFn) {
131
- cfg.catchFn({
132
- ...resp,
133
- err,
134
- })
135
- handled = true
136
- }
137
-
138
- cfg.finallyFn?.(resp)
139
-
140
- if (!handled) {
141
- throw err // rethrow
142
- }
108
+ }
109
+
110
+ cfg.finallyFn?.(resp)
111
+
112
+ return res
113
+ } catch (err) {
114
+ console.error(`@${decoratorName} ${methodSignature} catch:`, err)
115
+
116
+ const resp: PromiseDecoratorResp<PARAMS> = {
117
+ decoratorParams,
118
+ args,
119
+ key,
120
+ target,
121
+ decoratorName,
122
+ started,
123
+ }
124
+
125
+ let handled = false
126
+
127
+ if (cfg.catchFn) {
128
+ cfg.catchFn({
129
+ ...resp,
130
+ err,
143
131
  })
144
- // es2018 only
145
- // .finally(() => {})
146
- )
132
+ handled = true
133
+ }
134
+
135
+ cfg.finallyFn?.(resp)
136
+
137
+ if (!handled) {
138
+ throw err // rethrow
139
+ }
140
+ }
147
141
  }
148
142
 
149
143
  return pd
package/src/index.ts CHANGED
@@ -155,8 +155,18 @@ export * from './string/safeJsonStringify'
155
155
  import { PQueue, PQueueCfg } from './promise/pQueue'
156
156
  export * from './seq/seq'
157
157
  export * from './math/stack.util'
158
+ export * from './string/leven'
159
+ export * from './datetime/localDate'
160
+ export * from './datetime/localTime'
161
+ import { LocalDateConfig, LocalDateUnit } from './datetime/localDate'
162
+ import { LocalTimeConfig, LocalTimeUnit, LocalTimeComponents } from './datetime/localTime'
158
163
 
159
164
  export type {
165
+ LocalDateConfig,
166
+ LocalDateUnit,
167
+ LocalTimeConfig,
168
+ LocalTimeUnit,
169
+ LocalTimeComponents,
160
170
  AbortableMapper,
161
171
  AbortablePredicate,
162
172
  AbortableAsyncPredicate,
@@ -0,0 +1,86 @@
1
+ const array: number[] = []
2
+ const characterCodeCache: number[] = []
3
+
4
+ /* eslint-disable unicorn/prefer-code-point, no-bitwise */
5
+
6
+ /**
7
+ * Modified version of: https://github.com/sindresorhus/leven/
8
+ *
9
+ * Returns a Levenshtein distance between first and second word.
10
+ */
11
+ export function _leven(first: string, second: string): number {
12
+ if (first === second) {
13
+ return 0
14
+ }
15
+
16
+ const swap = first
17
+
18
+ // Swapping the strings if `a` is longer than `b` so we know which one is the
19
+ // shortest & which one is the longest
20
+ if (first.length > second.length) {
21
+ first = second
22
+ second = swap
23
+ }
24
+
25
+ let firstLength = first.length
26
+ let secondLength = second.length
27
+
28
+ // Performing suffix trimming:
29
+ // We can linearly drop suffix common to both strings since they
30
+ // don't increase distance at all
31
+ // Note: `~-` is the bitwise way to perform a `- 1` operation
32
+ while (firstLength > 0 && first.charCodeAt(~-firstLength) === second.charCodeAt(~-secondLength)) {
33
+ firstLength--
34
+ secondLength--
35
+ }
36
+
37
+ // Performing prefix trimming
38
+ // We can linearly drop prefix common to both strings since they
39
+ // don't increase distance at all
40
+ let start = 0
41
+
42
+ while (start < firstLength && first.charCodeAt(start) === second.charCodeAt(start)) {
43
+ start++
44
+ }
45
+
46
+ firstLength -= start
47
+ secondLength -= start
48
+
49
+ if (firstLength === 0) {
50
+ return secondLength
51
+ }
52
+
53
+ let bCharacterCode
54
+ let result: number
55
+ let temporary: number
56
+ let temporary2: number
57
+ let index = 0
58
+ let index2 = 0
59
+
60
+ while (index < firstLength) {
61
+ characterCodeCache[index] = first.charCodeAt(start + index)
62
+ array[index] = ++index
63
+ }
64
+
65
+ while (index2 < secondLength) {
66
+ bCharacterCode = second.charCodeAt(start + index2)
67
+ temporary = index2++
68
+ result = index2
69
+
70
+ for (index = 0; index < firstLength; index++) {
71
+ temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1
72
+ temporary = array[index]!
73
+ // eslint-disable-next-line no-multi-assign
74
+ result = array[index] =
75
+ temporary > result
76
+ ? temporary2 > result
77
+ ? result + 1
78
+ : temporary2
79
+ : temporary2 > temporary
80
+ ? temporary + 1
81
+ : temporary2
82
+ }
83
+ }
84
+
85
+ return result!
86
+ }