Skip to content

Feature/go #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ module.exports = {
'@typescript-eslint/no-non-null-assertion': 0,
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'prefer-const': ['error', { destructuring: 'all'}],
},
};

1 change: 1 addition & 0 deletions packages/tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"dependencies": {
"bignumber.js": "^9.1.1",
"dayjs": "^1.11.7",
"loglevel": "^1.8.1",
"tslib": "^2.5.0"
},
"typedoc": {
Expand Down
6 changes: 6 additions & 0 deletions packages/tools/src/const/event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const EVENT_BUS_EVENTS = {
warning: 'warning',
success: 'success',
error: 'error',
info: 'info',
} as const
1 change: 1 addition & 0 deletions packages/tools/src/const/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './bn'
export * from './event-bus'
23 changes: 0 additions & 23 deletions packages/tools/src/errors.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/tools/src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './problem'
export * from './runtime-error'
77 changes: 77 additions & 0 deletions packages/tools/src/errors/problem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import log from 'loglevel'

import { EventBus } from '@/events'
import type { ProblemConfig } from '@/types'

import { RuntimeError } from './runtime-error'

let config: ProblemConfig = {
eventBus: new EventBus(),
}

export class Problem {
/**
* `setConfig` overrides default config.
*/
public static setConfig(cfg: ProblemConfig): void {
config = { ...config, ...cfg }
}

/**
* `new` returns an error with the supplied message.
*/
public static new(message: string): RuntimeError {
return new RuntimeError(message)
}

/**
* `wrap` returns an error annotating err with a stack trace
* at the point wrap is called, and the supplied message.
*
*
* Fields can optionally be added. If provided, multiple fields will be merged.
*
* If err is null, Wrap returns null.
*/
public static wrap(
error: Error | RuntimeError | null | undefined,
message: string,
...errorFields: object[]
): RuntimeError | null {
return error ? new RuntimeError(message, error, ...errorFields) : null
}

/**
* `cause` returns the underlying cause of the error, if possible.
* If the error is null, null will be returned without further
* investigation.
*/
public static cause(error?: Error | null): Error | null {
if (!error) return null
if (!Problem.isRuntimeError(error)) return error
return error.originalError ? Problem.cause(error.originalError) : error
}

public static isRuntimeError(error?: Error): error is RuntimeError {
return error instanceof RuntimeError
}

/**
* `handle` handles provided error, error could be logged by default, and
* emitted via event bus if `eventBus` is set in the config.
*
* optional message parameter will be emitted with the error if presented
*/
public static handle(error: unknown, message?: string): void {
if (!(error instanceof Error)) return
config?.eventBus?.error?.({ error, message })
Problem.handleWithoutFeedback(error)
}

/**
* `handleWithoutFeedback` logs provided an error without emitting it via event bus.
*/
public static handleWithoutFeedback(error: Error | RuntimeError): void {
log.error(error)
}
}
69 changes: 69 additions & 0 deletions packages/tools/src/errors/runtime-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Problem } from './problem'

export class RuntimeError extends Error {
public name = 'RuntimeError'
public originalError?: Error
public errorFields: object[] = []

public constructor(errorOrMessage: Error | string)
public constructor(message: string, error: Error)
public constructor(message: string, error: Error, ...errorFields: object[])
public constructor(
errorOrMessage: Error | string,
error?: Error,
...errorFields: object[]
) {
const isErrorOrMessageString = typeof errorOrMessage === 'string'
const message = isErrorOrMessageString
? errorOrMessage
: errorOrMessage?.message

super(message)

this.originalError =
error || isErrorOrMessageString ? undefined : errorOrMessage

this.errorFields = errorFields
}

public toString() {
let name = this.name

const originalError = Problem.isRuntimeError(this.originalError)
? Problem.cause(this.originalError)
: this.originalError

if (originalError && !Problem.isRuntimeError(originalError)) {
name = `${name}: ${originalError.name}`
}

let message = this.message
let err = this.originalError
const fields = [...this.errorFields]

for (;;) {
if (!err) break

if (!Problem.isRuntimeError(err)) {
message += `: ${err.message}`
break
}

err = err.originalError
fields.push(...(err as RuntimeError).errorFields)
message += ` :${err!.message}`
}

const fieldObj = fields.reduce((acc, field) => {
acc = { ...acc, ...field }
return acc
}, {})

message = Object.entries(fieldObj).reduce((acc, [key, value]) => {
acc += ` ${key}: ${JSON.stringify(value)}`
return acc
}, message)

return `${name}: ${message}`
}
}
102 changes: 102 additions & 0 deletions packages/tools/src/events/event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import log from 'loglevel'

import { EVENT_BUS_EVENTS } from '@/const'
import type {
EventBusEvent,
EventBusEventEmitterEventMap,
EventBusEventHandler,
EventBusEventMap,
EventBusEventName,
EventHandler,
} from '@/types'

import { EventEmitter } from './event-emitter'

export class EventBus<AdditionalEventBusMap extends object = object> {
readonly #events: EventBusEventMap<AdditionalEventBusMap>
readonly #emitter: EventEmitter<
EventBusEventEmitterEventMap<AdditionalEventBusMap>
>
#backlog: EventBusEvent<EventBusEventMap<AdditionalEventBusMap>>[]

constructor(events?: AdditionalEventBusMap) {
this.#backlog = []
this.#emitter = new EventEmitter<
EventBusEventEmitterEventMap<AdditionalEventBusMap>
>()

this.#events = {
...EVENT_BUS_EVENTS,
...(events || {}),
} as EventBusEventMap<AdditionalEventBusMap>
}

public get events(): EventBusEventMap<AdditionalEventBusMap> {
return this.#events
}

public isEventExists(
event: EventBusEventName<AdditionalEventBusMap>,
): boolean {
const values = Object.values(
this.#events,
) as EventBusEventName<AdditionalEventBusMap>[]
return values.includes(event)
}

public on<Payload>(
event: EventBusEventName<EventBusEventMap<AdditionalEventBusMap>>,
handler: EventBusEventHandler<Payload>,
): void {
if (!this.isEventExists(event)) {
throw new Error(`EventBus.list has no ${event} event`)
}

const backloggedEvents = this.#backlog.filter(e => e.name === event)

for (const [index, eventObj] of backloggedEvents.entries()) {
handler(eventObj.payload as Payload)
this.#backlog.splice(index, 1)
log.debug(`Event ${event} is backlogged. Handling...`)
}
this.#emitter.on(event, handler as EventHandler<unknown>)
}

public emit<Payload>(
event: EventBusEventName<EventBusEventMap<AdditionalEventBusMap>>,
payload?: Payload,
): void {
if (!this.isEventExists(event)) {
throw new Error(`EventBus.list has no ${event.toString()} event`)
}

this.#emitter.emit(event, payload)
}

public reset<Payload>(
event: EventBusEventName<EventBusEventMap<AdditionalEventBusMap>>,
handler: EventBusEventHandler<Payload>,
): void {
if (!this.isEventExists(event)) {
throw new Error(`EventBus.list has no ${event.toString()} event`)
}
this.#emitter.off(event, handler as EventHandler<unknown>)
this.#backlog = []
}

public success<Payload>(payload: Payload): void {
this.#emitter.emit(this.#events.success, payload)
}

public warning<Payload>(payload: Payload): void {
this.#emitter.emit(this.#events.warning, payload)
}

public error<Payload>(payload: Payload): void {
this.#emitter.emit(this.#events.error, payload)
}

public info<Payload>(payload: Payload): void {
this.#emitter.emit(this.#events.info, payload)
}
}
2 changes: 2 additions & 0 deletions packages/tools/src/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './event-bus'
export * from './event-emitter'
53 changes: 53 additions & 0 deletions packages/tools/src/go.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { go } from './go'

describe('preforms go unit test', () => {
describe('async callback should return', () => {
test('null result if error thrown', async () => {
const [err, result] = await go(async () => {
throw new Error('test error')
})

expect(err).toBeInstanceOf(Error)
expect(result).toBeNull()
})

test('result if no error thrown', async () => {
const [err, result] = await go(async () => {
return 'test result'
})
expect(err).toBeNull()
expect(result).toBe('test result')
})
})

describe('sync callback should return', () => {
test('null result if error thrown', async () => {
const [err, result] = await go(() => {
throw new Error('test error')
})
expect(err).toBeInstanceOf(Error)
expect(result).toBeNull()
})

test('result if no error thrown', async () => {
const [err, result] = await go(() => {
return 'test result'
})
expect(err).toBeNull()
expect(result).toBe('test result')
})
})

test('should redeclare error during few executions', async () => {
let [err, result] = await go(async () => {
throw new Error('test error')
})

expect(err).toBeInstanceOf(Error)
expect(result).toBeNull()
;[err] = await go(async () => {
return 'success'
})
expect(err).toBeNull()
})
})
10 changes: 10 additions & 0 deletions packages/tools/src/go.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const go = async <C extends () => unknown, E = Error>(
cb: C,
): Promise<[E | null, Awaited<ReturnType<C>> | null]> => {
try {
const res = await cb()
return [null, res as Awaited<ReturnType<C>>]
} catch (e) {
return [e as E, null]
}
}
6 changes: 3 additions & 3 deletions packages/tools/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export * from '@/bn'
export * from '@/duration'
export * from '@/enums'
export * from '@/errors'
export * from '@/event-emitter'
export * from '@/events'
export * from '@/go'
export * from '@/helpers'
export * from '@/math'
export * from '@/time'
export * from '@/types'
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { BN } from '@/bn/bn'
import { toDecimals } from '@/bn/decimals'
import { BN_ROUNDING } from '@/enums'

import { BN } from './bn'
import { toDecimals } from './decimals'

export const round = (bn: BN, decimals: number, mode: BN_ROUNDING) => {
const precisioned = toDecimals(bn.raw, BN.precision, decimals + 1).toString()

Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions packages/tools/src/time/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './duration'
export * from './time'
File renamed without changes.
File renamed without changes.
Loading