Skip to content

Commit

Permalink
Merge branch 'master' into splits
Browse files Browse the repository at this point in the history
  • Loading branch information
andrepolischuk committed Aug 14, 2024
2 parents b2edf7a + 8b8eb51 commit 461accb
Show file tree
Hide file tree
Showing 46 changed files with 1,163 additions and 3 deletions.
20 changes: 20 additions & 0 deletions .size-limit.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
[
{
"path": "packages/async/dist/index.js",
"limit": "400 B"
},
{
"path": "packages/cookie-storage/dist/index.js",
"limit": "990 B"
},
{
"path": "packages/debug/dist/index.js",
"limit": "3.4 KB"
},
{
"path": "packages/dom/dist/index.js",
"limit": "500 B"
},
{
"path": "packages/local-storage/dist/index.js",
"limit": "290 B"
},
{
"path": "packages/react/dist/index.js",
"limit": "4.25 KB"
},
{
"path": "packages/session-storage/dist/index.js",
"limit": "290 B"
},
{
"path": "packages/splits/dist/index.js",
"limit": "1.6 KB"
},
{
"path": "packages/url/dist/index.js",
"limit": "675 B"
}
]
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ Common utils used by Rambler team

## Packages

- [@rambler-tech/async](packages/async)
- [@rambler-tech/cookie-storage](packages/cookie-storage)
- [@rambler-tech/debug](packages/debug)
- [@rambler-tech/dom](packages/dom)
- [@rambler-tech/lhci-report](packages/lhci-report)
- [@rambler-tech/local-storage](packages/local-storage)
- [@rambler-tech/react](packages/react)
- [@rambler-tech/session-storage](packages/session-storage)
- [@rambler-tech/lhci-report](packages/lhci-report)
- [@rambler-tech/splits](packages/splits)
- [@rambler-tech/url](packages/url)

## Contributing

Expand Down
10 changes: 10 additions & 0 deletions packages/async/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

# 0.1.0 (2024-08-14)

### Features

- **async:** add async tools ([946d5ba](https://github.com/rambler-digital-solutions/rambler-common/commit/946d5baf89b77fa07f9845ef68e3d8f5b6d7dd5f))
15 changes: 15 additions & 0 deletions packages/async/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Async

Async tools

## Install

```
npm install -D @rambler-tech/async
```

or

```
yarn add -D @rambler-tech/async
```
88 changes: 88 additions & 0 deletions packages/async/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {retry, wait} from '.'

test('retry resolved promise', async () => {
const fn = jest.fn((...args) => Promise.resolve(args))
const args = [1, 2, 3]
const result = await retry(fn)(...args)

expect(result).toEqual(args)
expect(fn).toHaveBeenCalledTimes(1)
})

test('retry rejected and last resolved promise', async () => {
let counter = 0
const fn = jest.fn((...args) =>
counter++ > 1 ? Promise.resolve(args) : Promise.reject(new Error('error'))
)
const args = [1, 2, 3]
const result = await retry(fn, {retries: 3, timeout: 10})(...args)

expect(result).toEqual(args)
expect(fn).toHaveBeenCalledTimes(3)
})

test('retry rejected and ignore rejected with specific error', async () => {
let counter = 0
const fn = jest.fn(() =>
Promise.reject(
new Error(counter++ > 0 ? 'aborted by timeout' : 'yet another error')
)
)

const error = await retry(fn, {
retries: 3,
timeout: 10,
shouldRetry: (error) => !error.toString().match(/aborted/)
})().catch((error) => error)

expect(error.message).toBe('aborted by timeout')
expect(fn).toHaveBeenCalledTimes(2)
})

test('retry rejected promise', async () => {
const fn = jest.fn(() => Promise.reject(new Error('failed')))
const error = await retry(fn, {retries: 3, timeout: 10})().catch(
(error) => error
)

expect(error.message).toBe('failed')
expect(fn).toHaveBeenCalledTimes(3)
})

test('abort retry', async () => {
const abortController = new AbortController()
const fn = jest.fn(() => Promise.reject(new Error('failed')))
const promise = retry(fn, {
retries: 3,
timeout: 10,
signal: abortController.signal
})()

// NOTE: simulate microtask to abort wait after first attempt
await Promise.resolve().then(() => abortController.abort())

const error = await promise.catch((error) => error)

expect(error.message).toBe('The user aborted a timeout.')
expect(fn).toHaveBeenCalledTimes(1)
})

test('wait timeout', () => {
jest.useFakeTimers()
jest.spyOn(global, 'setTimeout')

const promise = wait(1000)

jest.advanceTimersByTime(1000)

expect(promise).resolves.toBeUndefined()
})

test('aborted wait timeout', () => {
const abortController = new AbortController()
const promise = wait(1000, abortController.signal)

abortController.abort()

expect(promise).rejects.toThrow('The user aborted a timeout.')
})
58 changes: 58 additions & 0 deletions packages/async/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable import/no-unused-modules */

const RETRIES_LEFT = 3
const INTERVAL = 500

export type PromiseFactory<T> = (...args: any[]) => Promise<T>

/** Retry options */
export interface RetryOptions {
/** Maximum amount of times to retry the operation, default is 3 */
retries?: number
/** Number of milliseconds before starting the retry, default is 500 */
timeout?: number
/** Check an error to need retry, by default retry on every error */
shouldRetry?: (error: Error) => boolean
/** AbortSignal instance to abort retry via an AbortController */
signal?: AbortSignal
}

/** Retry function call */
export function retry<T>(
factory: PromiseFactory<T>,
options: RetryOptions = {}
): PromiseFactory<T> {
let {retries = RETRIES_LEFT} = options
const {timeout = INTERVAL, shouldRetry = () => true, signal} = options

async function call(...args: any[]): Promise<T> {
try {
return await factory(...args)
} catch (error: any) {
if (--retries < 1 || !shouldRetry(error)) {
throw error
}

await wait(timeout, signal)

return call(...args)
}
}

return (...args: any[]): Promise<T> => call(...args)
}

/** Wait function call */
export function wait(timeout: number, signal?: AbortSignal): Promise<void> {
return new Promise<void>((resolve, reject) => {
const timeoutId = window.setTimeout(resolve, timeout)

signal?.addEventListener('abort', () => {
if (timeoutId) {
clearTimeout(timeoutId)
}

reject(new DOMException('The user aborted a timeout.', 'AbortError'))
})
})
}
12 changes: 12 additions & 0 deletions packages/async/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@rambler-tech/async",
"version": "0.1.0",
"main": "dist",
"module": "dist",
"types": "dist/index.d.ts",
"license": "MIT",
"sideEffects": false,
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions packages/async/tsconfig.json
1 change: 1 addition & 0 deletions packages/async/typedoc.json
20 changes: 20 additions & 0 deletions packages/debug/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [0.1.2](https://github.com/rambler-digital-solutions/rambler-common/compare/@rambler-tech/[email protected]...@rambler-tech/[email protected]) (2024-08-14)

**Note:** Version bump only for package @rambler-tech/debug

## [0.1.1](https://github.com/rambler-digital-solutions/rambler-common/compare/@rambler-tech/[email protected]...@rambler-tech/[email protected]) (2024-08-14)

### Bug Fixes

- **debug:** lock deps ([0e153ee](https://github.com/rambler-digital-solutions/rambler-common/commit/0e153eef963738c66d0a6ff0f1696f1ea775fd2c))

# 0.1.0 (2024-08-14)

### Features

- **debug:** add debug ([93c27f6](https://github.com/rambler-digital-solutions/rambler-common/commit/93c27f6ac7083b6609d762c5decbc02c7006773a))
15 changes: 15 additions & 0 deletions packages/debug/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Debug

Debug management based on [debug](https://github.com/debug-js/debug).

## Install

```
npm install -D @rambler-tech/debug
```

or

```
yarn add -D @rambler-tech/debug
```
56 changes: 56 additions & 0 deletions packages/debug/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {getItem, setItem} from '@rambler-tech/local-storage'
import {initDebug} from '.'

class LocalStorage implements LocalStorage {
map: {[key: string]: any} = {}
getItem(key: string): void {
return this.map[key]
}
setItem(key: string, value: any): void {
this.map[key] = value
}
removeItem(key: string): void {
delete this.map[key]
}
}

Object.defineProperty(window, 'localStorage', {
value: new LocalStorage()
})

test('reset local storage debug value', () => {
setItem('debug', 'test', {raw: true})

expect(getItem('debug', {raw: true})).toBe('test')

initDebug('https://foobar.ru?debug=unset')

expect(getItem('debug', {raw: true})).toBeNull()
})

test('use window.location.href value', () => {
Object.defineProperty(window, 'location', {
value: {
href: 'https://test.ru?debug=bar'
},
writable: true
})

setItem('debug', 'foo', {raw: true})

expect(getItem('debug', {raw: true})).toBe('foo')

initDebug()

expect(getItem('debug', {raw: true})).toBe('bar')
})

test('use debug value from initDebug arg', () => {
setItem('debug', 'test', {raw: true})

expect(getItem('debug', {raw: true})).toBe('test')

initDebug('https://foobar.ru?debug=foo')

expect(getItem('debug', {raw: true})).toBe('foo')
})
43 changes: 43 additions & 0 deletions packages/debug/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable import/no-unused-modules */
import {getUrlParams} from '@rambler-tech/url'
import {removeItem, setItem, getItem} from '@rambler-tech/local-storage'

const RESET_DEBUG_LOCAL_STORAGE_VALUE = 'unset'

/**
* Init debug mode from URL
*
* ```ts
* initDebug('https://example.com?debug=value')
* ```
*
* Possible value:
* * `*` - Include all debug namespaces
* * `unset` - Reset debug value from local storage
* * `value` - Write debug value into local storage
*/
export function initDebug(href?: string) {
if (typeof window === 'undefined') {
return
}

const {debug: queryParam} = getUrlParams(href ?? window.location.href)
const storageParam = getItem('debug', {raw: true})

if (
!queryParam ||
typeof queryParam !== 'string' ||
storageParam === queryParam ||
(!storageParam && queryParam === RESET_DEBUG_LOCAL_STORAGE_VALUE)
) {
return
}

if (queryParam === RESET_DEBUG_LOCAL_STORAGE_VALUE) {
removeItem('debug')
} else {
setItem('debug', queryParam, {raw: true})
}
}

export {default as createDebug} from 'debug'
Loading

0 comments on commit 461accb

Please sign in to comment.