@beecode/msh-util 1.2.0 → 1.2.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.
@@ -0,0 +1,89 @@
1
+ import { DurationUnit, DurationUnitType, TimeUtil } from 'src/time-util'
2
+
3
+ describe('TimeUtil', () => {
4
+ const constantNowDate = new Date('2022-01-01T00:00:00.000Z')
5
+ const constantNowUnix = 1640995200000 // 2022-01-01T00:00:00 UTC
6
+ const constantNowUnixSec = 1640995200 // 2022-01-01T00:00:00 UTC
7
+ const timeUtil = new TimeUtil()
8
+
9
+ beforeAll(() => {
10
+ jest.useFakeTimers('modern' as any)
11
+ jest.setSystemTime(constantNowDate.getTime())
12
+ })
13
+
14
+ afterAll(() => {
15
+ jest.useRealTimers()
16
+ })
17
+
18
+ describe('now', () => {
19
+ it('should return mocked date', () => {
20
+ expect(timeUtil.now().toISOString()).toEqual(constantNowDate.toISOString())
21
+ })
22
+ })
23
+
24
+ describe('dateToUnix', () => {
25
+ it('should convert constant date to constant unix', () => {
26
+ expect(timeUtil.dateToUnix(timeUtil.now())).toEqual(constantNowUnix)
27
+ })
28
+ })
29
+
30
+ describe('dateToUnixSec', () => {
31
+ it('should convert constant date to constant unixSec', () => {
32
+ expect(timeUtil.dateToUnixSec(timeUtil.now())).toEqual(constantNowUnixSec)
33
+ })
34
+ })
35
+
36
+ describe('unixToDate', () => {
37
+ it('should convert constant date to constant unix', () => {
38
+ expect(timeUtil.unixToDate(constantNowUnix)).toEqual(timeUtil.now())
39
+ })
40
+ })
41
+
42
+ describe('unixSecToDate', () => {
43
+ it('should convert constant date to constant unixSec', () => {
44
+ expect(timeUtil.unixSecToDate(constantNowUnixSec)).toEqual(timeUtil.now())
45
+ })
46
+ })
47
+
48
+ describe('addToDate', () => {
49
+ it.each([
50
+ [DurationUnit.MILLISECOND, 1, new Date('2022-01-01T00:00:00.001Z')],
51
+ [DurationUnit.SECOND, 1, new Date('2022-01-01T00:00:01.000Z')],
52
+ [DurationUnit.MINUTE, 1, new Date('2022-01-01T00:01:00.000Z')],
53
+ [DurationUnit.HOUR, 1, new Date('2022-01-01T01:00:00.000Z')],
54
+ [DurationUnit.DAY, 1, new Date('2022-01-02T00:00:00.000Z')],
55
+ [DurationUnit.WEEK, 1, new Date('2022-01-08T00:00:00.000Z')],
56
+ [DurationUnit.MONTH, 1, new Date('2022-02-01T00:00:00.000Z')],
57
+ [DurationUnit.YEAR, 1, new Date('2023-01-01T00:00:00.000Z')],
58
+ [DurationUnit.MILLISECOND, -1, new Date('2021-12-31T23:59:59.999Z')],
59
+ [DurationUnit.SECOND, -1, new Date('2021-12-31T23:59:59.000Z')],
60
+ [DurationUnit.MINUTE, -1, new Date('2021-12-31T23:59:00.000Z')],
61
+ [DurationUnit.HOUR, -1, new Date('2021-12-31T23:00:00.000Z')],
62
+ [DurationUnit.DAY, -1, new Date('2021-12-31T00:00:00.000Z')],
63
+ [DurationUnit.WEEK, -1, new Date('2021-12-25T00:00:00.000Z')],
64
+ [DurationUnit.MONTH, -1, new Date('2021-12-01T00:00:00.000Z')],
65
+ [DurationUnit.YEAR, -1, new Date('2021-01-01T00:00:00.000Z')],
66
+ ['MILLISECOND', 1, new Date('2022-01-01T00:00:00.001Z')],
67
+ ['SECOND', 1, new Date('2022-01-01T00:00:01.000Z')],
68
+ ['MINUTE', 1, new Date('2022-01-01T00:01:00.000Z')],
69
+ ['HOUR', 1, new Date('2022-01-01T01:00:00.000Z')],
70
+ ['DAY', 1, new Date('2022-01-02T00:00:00.000Z')],
71
+ ['WEEK', 1, new Date('2022-01-08T00:00:00.000Z')],
72
+ ['MONTH', 1, new Date('2022-02-01T00:00:00.000Z')],
73
+ ['YEAR', 1, new Date('2023-01-01T00:00:00.000Z')],
74
+ ['MILLISECOND', -1, new Date('2021-12-31T23:59:59.999Z')],
75
+ ['SECOND', -1, new Date('2021-12-31T23:59:59.000Z')],
76
+ ['MINUTE', -1, new Date('2021-12-31T23:59:00.000Z')],
77
+ ['HOUR', -1, new Date('2021-12-31T23:00:00.000Z')],
78
+ ['DAY', -1, new Date('2021-12-31T00:00:00.000Z')],
79
+ ['WEEK', -1, new Date('2021-12-25T00:00:00.000Z')],
80
+ ['MONTH', -1, new Date('2021-12-01T00:00:00.000Z')],
81
+ ['YEAR', -1, new Date('2021-01-01T00:00:00.000Z')],
82
+ ] as [DurationUnitType | DurationUnit, number, Date][])(
83
+ '%#. should increase date by unit %s and value %s and get date $s',
84
+ (unit, value, expected) => {
85
+ expect(timeUtil.addToDate({ date: timeUtil.now(), unit, value })).toEqual(expected)
86
+ }
87
+ )
88
+ })
89
+ })
@@ -0,0 +1,98 @@
1
+ import add from 'date-fns/add'
2
+ import addMilliseconds from 'date-fns/addMilliseconds'
3
+ import format from 'date-fns/format'
4
+ import parse from 'date-fns/parse'
5
+
6
+ export enum DurationUnit {
7
+ MILLISECOND = 'MILLISECOND',
8
+ SECOND = 'SECOND',
9
+ MINUTE = 'MINUTE',
10
+ HOUR = 'HOUR',
11
+ DAY = 'DAY',
12
+ WEEK = 'WEEK',
13
+ MONTH = 'MONTH',
14
+ YEAR = 'YEAR',
15
+ }
16
+
17
+ export type DurationUnitType = `${DurationUnit}`
18
+
19
+ export class TimeUtil {
20
+ /**
21
+ * return date object with the current time
22
+ * @return {Date}
23
+ * @example
24
+ * console.log(new TimeUtil().now().toISOString()) // 2023-03-08T19:45:01.991Z
25
+ */
26
+ now(): Date {
27
+ return new Date()
28
+ }
29
+
30
+ /**
31
+ * Convert date object to unix timestamp (milliseconds)
32
+ * @param {Date} date
33
+ * @return {number}
34
+ * @example
35
+ * // timeUtil.now().toISOString() === 2023-03-08T19:45:01.991Z
36
+ * const timeUtil = new TimeUtil()
37
+ * console.log(timeUtil.dateToUnix(timeUtil.now())) // 1678304701991
38
+ */
39
+ dateToUnix(date: Date): number {
40
+ return +format(date, 'T')
41
+ }
42
+
43
+ /**
44
+ * Convert date object to unix timestamp (seconds)
45
+ * @param {Date} date
46
+ * @return {number}
47
+ * @example
48
+ * // timeUtil.now().toISOString() === 2023-03-08T19:45:01.991Z
49
+ * const timeUtil = new TimeUtil()
50
+ * console.log(timeUtil.dateToUnix(timeUtil.now())) // 1678304701
51
+ */
52
+ dateToUnixSec(date: Date): number {
53
+ return +format(date, 't')
54
+ }
55
+
56
+ /**
57
+ * Convert unix timestamp (milliseconds) to date object
58
+ * @param {number} unix
59
+ * @return {Date}
60
+ * @example
61
+ * const timeUtil = new TimeUtil()
62
+ * console.log(timeUtil.unixToDate(1678304701991).toISOString()) // 2023-03-08T19:45:01.991Z
63
+ */
64
+ unixToDate(unix: number): Date {
65
+ return parse(unix.toString(), 'T', this.now())
66
+ }
67
+
68
+ /**
69
+ * Convert unix timestamp (seconds) to date object
70
+ * @param {number} unix
71
+ * @return {Date}
72
+ * @example
73
+ * const timeUtil = new TimeUtil()
74
+ * console.log(timeUtil.unixToDate(1678304701).toISOString()) // 2023-03-08T19:45:01.000Z
75
+ */
76
+ unixSecToDate(unix: number): Date {
77
+ return parse(unix.toString(), 't', this.now())
78
+ }
79
+
80
+ /**
81
+ * Change the value of date by unit/value pare.
82
+ * @param {{units: DurationUnitType, value: number, date: Date}} params
83
+ * @return {Date}
84
+ * @example
85
+ * // timeUtil.now().toISOString() === 2023-03-08T19:45:01.991Z
86
+ * const timeUtil = new TimeUtil()
87
+ * console.log(timeUtil.addToDate({date: timeUtil.now(), unit: 'DAY', value: 1 }).toISOString()) // 2023-03-09T19:45:01.991Z
88
+ * console.log(timeUtil.addToDate({date: timeUtil.now(), unit: DurationUnit.MONTH, value: -1 }).toISOString()) //2023-02-08T19:45:01.991Z
89
+ */
90
+ addToDate(params: { unit: DurationUnitType | DurationUnit; value: number; date: Date }): Date {
91
+ const { date, unit, value } = params
92
+ if (`${unit}` === 'MILLISECOND') {
93
+ return addMilliseconds(date, value)
94
+ }
95
+
96
+ return add(date, { [`${unit.toLowerCase()}s`]: value })
97
+ }
98
+ }
@@ -0,0 +1,65 @@
1
+ import { timeout } from 'src/timeout'
2
+
3
+ describe('timeout', () => {
4
+ const fn_one = jest.fn()
5
+ const fn_two = jest.fn()
6
+ const fn_three = jest.fn()
7
+ const fn_onResolve = jest.fn()
8
+
9
+ const timeoutImplementation = async (): Promise<void> => {
10
+ fn_one()
11
+ await timeout(1000)
12
+ fn_two()
13
+ await timeout(1000)
14
+ fn_three()
15
+ }
16
+
17
+ beforeEach(() => {
18
+ jest.useFakeTimers()
19
+ })
20
+
21
+ afterEach(() => {
22
+ jest.clearAllTimers()
23
+ jest.useRealTimers()
24
+ jest.resetAllMocks()
25
+ })
26
+
27
+ it('should function one by one with pause of 1000ms', async () => {
28
+ expect(fn_one).toHaveBeenCalledTimes(0)
29
+ expect(fn_two).toHaveBeenCalledTimes(0)
30
+ expect(fn_three).toHaveBeenCalledTimes(0)
31
+ expect(fn_onResolve).toHaveBeenCalledTimes(0)
32
+
33
+ const promise1 = timeoutImplementation().then(() => {
34
+ return fn_onResolve()
35
+ })
36
+
37
+ expect(fn_one).toHaveBeenCalledTimes(1)
38
+ expect(fn_two).toHaveBeenCalledTimes(0)
39
+ expect(fn_three).toHaveBeenCalledTimes(0)
40
+ expect(fn_onResolve).toHaveBeenCalledTimes(0)
41
+
42
+ jest.advanceTimersByTime(1000)
43
+ await Promise.resolve()
44
+
45
+ expect(fn_one).toHaveBeenCalledTimes(1)
46
+ expect(fn_two).toHaveBeenCalledTimes(1)
47
+ expect(fn_three).toHaveBeenCalledTimes(0)
48
+ expect(fn_onResolve).toHaveBeenCalledTimes(0)
49
+
50
+ jest.advanceTimersByTime(1000)
51
+ await Promise.resolve()
52
+
53
+ expect(fn_one).toHaveBeenCalledTimes(1)
54
+ expect(fn_two).toHaveBeenCalledTimes(1)
55
+ expect(fn_three).toHaveBeenCalledTimes(1)
56
+ expect(fn_onResolve).toHaveBeenCalledTimes(0)
57
+
58
+ await promise1
59
+
60
+ expect(fn_one).toHaveBeenCalledTimes(1)
61
+ expect(fn_two).toHaveBeenCalledTimes(1)
62
+ expect(fn_three).toHaveBeenCalledTimes(1)
63
+ expect(fn_onResolve).toHaveBeenCalledTimes(1)
64
+ })
65
+ })
package/src/timeout.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ *
3
+ * @param {number} ms The time, in milliseconds that the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, the next event cycle.
4
+ * @return {Promise<void>}
5
+ * @example
6
+ * const lightBlink = (): void => {
7
+ * turnLightOn()
8
+ * timeout(3000) // wait for 3 seconds
9
+ * turnLightOff()
10
+ * timeout(3000) // wait for 3 seconds
11
+ * turnLightOn()
12
+ * }
13
+ */
14
+ export const timeout = (ms: number): Promise<void> => {
15
+ return new Promise((resolve) => setTimeout(resolve, ms))
16
+ }
@@ -0,0 +1,20 @@
1
+ import { typeUtil } from 'src/type-util'
2
+
3
+ describe('typeUtil', () => {
4
+ describe('exhaustiveMessage', () => {
5
+ it('should return string', () => {
6
+ const testMessage = 'testMessage'
7
+ // @ts-ignore
8
+ expect(typeUtil.exhaustiveMessage(testMessage, '')).toEqual(testMessage)
9
+ })
10
+ })
11
+ describe('exhaustiveError', () => {
12
+ it('should return string', () => {
13
+ const testMessage = 'testMessage'
14
+ // @ts-ignore
15
+ const error = typeUtil.exhaustiveError(testMessage, '')
16
+ expect(error).toBeInstanceOf(Error)
17
+ expect(error.message).toEqual(testMessage)
18
+ })
19
+ })
20
+ })
@@ -0,0 +1,54 @@
1
+ export const typeUtil = {
2
+ /**
3
+ * This is the similar to exhaustiveMessage, but instead of message we are returning error so we can throw it
4
+ * @param {string} message
5
+ * @param {never} _
6
+ * @return {Error}
7
+ * @example
8
+ * export type Animal = 'cat' | 'dog' | 'bird';
9
+ *
10
+ * export const makeSound = (animal: Animal): string => {
11
+ * switch (animal) {
12
+ * case 'cat':
13
+ * return 'Meow'
14
+ * case 'dog':
15
+ * return 'Woof'
16
+ * case 'bird':
17
+ * return 'Tweet'
18
+ * default:
19
+ * throw typeUtil.exhaustiveError('Unknown animal [animal]', animal)
20
+ * }
21
+ * }
22
+ */
23
+ exhaustiveError: (message: string, _: never): Error => {
24
+ return new Error(message)
25
+ },
26
+
27
+ /**
28
+ * In TypeScript, exhaustiveMessage is a technique that can be used with switch statements to ensure that all possible cases are handled.
29
+ *
30
+ * When using switch statements, it is common to have a default case that handles any unanticipated cases. However, sometimes it is important to ensure that all cases are explicitly handled to avoid potential errors or bugs in the code.
31
+ * @param {string} message
32
+ * @param {never} _
33
+ * @return {string}
34
+ * @example
35
+ * export type Animal = 'cat' | 'dog' | 'bird';
36
+ *
37
+ * export const makeSound = (animal: Animal): string => {
38
+ * switch (animal) {
39
+ * case 'cat':
40
+ * return 'Meow'
41
+ * case 'dog':
42
+ * return 'Woof'
43
+ * case 'bird':
44
+ * return 'Tweet'
45
+ * default:
46
+ * console.error(new TypeUtil().exhaustiveMessage('Unknown animal [animal]', animal))
47
+ * return 'unknown sound'
48
+ * }
49
+ * }
50
+ */
51
+ exhaustiveMessage: (message: string, _: never): string => {
52
+ return message
53
+ },
54
+ }
@@ -0,0 +1 @@
1
+ export type AnyFunction<T> = (...args: any[]) => T
@@ -0,0 +1 @@
1
+ export type AnyFunctionNoParams<T> = () => T
@@ -0,0 +1 @@
1
+ export type AnyFunctionPromiseNoParams<T> = () => Promise<T>
@@ -0,0 +1 @@
1
+ export type AnyFunctionPromise<T> = (...args: any[]) => Promise<T>
@@ -0,0 +1,2 @@
1
+ /* istanbul ignore file */
2
+ import 'jest-extended'