Skip to content

Commit

Permalink
feat(ponyfill): AggregateError.prototype.cause support
Browse files Browse the repository at this point in the history
  • Loading branch information
unicornware committed Nov 23, 2022
1 parent c5c99eb commit d321773
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 39 deletions.
1 change: 1 addition & 0 deletions .dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ gpgsign
graphqlrc
hmarr
iife
instanceof
keyid
larsgw
lcov
Expand Down
1 change: 1 addition & 0 deletions .markdownlint.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"div",
"figcaption",
"figure",
"h4",
"img",
"script",
"small",
Expand Down
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ This package is an ECMAScript Proposal spec-compliant [ponyfill][2] for [`Aggreg

## When should I use this?

Use this package when you want to throw `AggregateError` objects in unsupported ECMAScript environments (`< es2021`).
Use this package when you want to throw `AggregateError` objects in unsupported environments (`< es2021`).

## Install

Expand Down Expand Up @@ -56,20 +56,40 @@ import AggregateError from '@flex-development/aggregate-error-ponyfill'
try {
throw new AggregateError([new Error('some error')], 'oh no!')
} catch (e) {
console.error(e.message) // 'oh no!'
console.error(e.name) // 'AggregateError'
console.error(e.errors) // [Error: 'some error']
console.debug(e instanceof AggregateError) // true
console.error(e.name) // 'AggregateError'
console.error(e.message) // 'oh no!'
console.error(e.errors) // [Error: 'some error']
}
```

## API

This package exports no identifiers. The default export is `AggregateError`.

### `new AggregateError<T>(errors: Iterable<T>, message?: string)`
### <h4>`new AggregateError<T, C>(errors: Iterable<T>, message?: string, options?: Options<C>)`</h4>

Wrap several errors in a single error so that multiple errors can be reported by an operation.

#### `errors`

An iterable of errors.

#### `message`

An optional human-readable description of the aggregate error.

#### `options`

An object that has the following properties:

##### `cause`

The specific cause of the aggregate error.

When catching and re-throwing an error with a more-specific or useful error message, this property can be used to pass
the original error.

## Types

This package is fully typed with [TypeScript][3].
Expand Down
9 changes: 5 additions & 4 deletions src/__tests__/ponyfill.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ import TestSubject from '../ponyfill'
describe('unit:ponyfill', () => {
it('should create spec-compliant AggregateError', () => {
// Arrange
const errors = [new SyntaxError(faker.lorem.sentence()), { code: 400 }]
const message = faker.lorem.sentence()
const cause = new Error('The server responded with a 500 status')
const symptom = new Error('The message failed to send')
const errors = [symptom, cause]

// Act
const result = new TestSubject(errors, message)
const result = new TestSubject(errors, symptom.message, { cause })

// Expect
expect(result).to.be.an.instanceof(Error)
expect(result.errors).to.deep.equal(errors)
expect(result.message).to.equal(message)
expect(result.message).to.equal(symptom.message)
expect(result.name).to.equal('AggregateError')
})

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
* @module aggregate-error-ponyfill
*/

export type { default as Options } from './options'
export { default } from './ponyfill'
17 changes: 17 additions & 0 deletions src/options-get-iterator-method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @file AggregateError - GetIteratorMethodOptions
* @module aggregate-error-ponyfill/ponyfill/GetIteratorMethodOptions
*/

import type getIteratorMethod from 'es-abstract/helpers/getIteratorMethod'

/**
* Iterator method creation options type.
*
* @internal
*
* @see {@linkcode getIteratorMethod}
*/
type GetIteratorMethodOptions = Parameters<typeof getIteratorMethod>['0']

export type { GetIteratorMethodOptions as default }
25 changes: 25 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @file AggregateError - Options
* @module aggregate-error-ponyfill/ponyfill/Options
*/

/**
* `AggregateError` options.
*
* @see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/AggregateError/AggregateError#parameters
*
* @template Cause - Error cause type
*/
interface Options<Cause = unknown> {
/**
* The specific cause of the error.
*
* When catching and re-throwing an error with a more-specific or useful error
* message, this property can be used to pass the original error.
*
* @see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
*/
cause?: Cause
}

export type { Options as default }
68 changes: 39 additions & 29 deletions src/ponyfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,64 +4,77 @@
*/

import AdvanceStringIndex from 'es-abstract/2021/AdvanceStringIndex'
import CreateProperty from 'es-abstract/2021/CreateDataProperty'
import CreatePropertyOrThrow from 'es-abstract/2021/CreateDataPropertyOrThrow'
import GetMethod from 'es-abstract/2021/GetMethod'
import IsArray from 'es-abstract/2021/IsArray'
import IterableToList from 'es-abstract/2021/IterableToList'
import Type from 'es-abstract/2021/Type'
import getIteratorMethod from 'es-abstract/helpers/getIteratorMethod'
import type Options from './options'
import type GetIteratorMethodOptions from './options-get-iterator-method'

/**
* Iterator method options type.
* The `AggregateError` object represents an error when several errors need to
* be wrapped in a single error.
*
* @see {@link getIteratorMethod}
*/
type GetIteratorMethodOptions = Parameters<typeof getIteratorMethod>['0']

/**
* A single error that represents a group of errors.
*
* It is thrown when multiple errors need to be reported by an operation, e.g.
* by [`Promise.any()`][1] when all promises passed to it reject.
* It is thrown when multiple errors need to be reported by an operation, for
* example by [`Promise.any()`][1] when all promises passed to it reject.
*
* [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
*
* @see https://tc39.es/proposal-promise-any#sec-aggregate-error-objects
* @see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
*
* @template T - Aggregated error type
* @template C - Error cause type
*
* @class
* @extends {Error}
*/
class AggregateError<T = any> extends Error {
class AggregateError<T = any, C = unknown> extends Error {
/**
* Error cause.
*
* @see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
*
* @public
* @member {C | undefined} cause
*/
public cause?: C

/**
* @public
* @member {T[]} errors - Aggregated errors
* @member {T[]} errors - Array containing aggregated errors
*/
public errors: T[]
public errors: T[] = []

/**
* @public
* @override
* @readonly
* @member {'AggregateError'} name - Error subclass name
* @member {string} name - Error name
*/
public override readonly name: 'AggregateError' = 'AggregateError'
public override readonly name: string = 'AggregateError'

/**
* Creates a single error representing `errors`.
* Creates an error for several errors that need to be wrapped in a single
* error.
*
* @example
* new AggregateError([new Error('some error')])
* @example
* new AggregateError([new Error('err1'), new Error('err2')], 'oh no!')
*
* @param {Iterable<T>} errors - Aggregated errors
* @param {string} [message] - Human-readable error message
* @param {Iterable<T>} errors - An iterable of errors
* @param {string} [message] - Human-readable description of the error
* @param {Options<C>} [options] - Error options
* @param {C} [options.cause] - The original cause of the error
*/
constructor(errors: Iterable<T>, message?: string) {
constructor(errors: Iterable<T>, message?: string, options?: Options<C>) {
super(message)

/**
* Iterator method options.
* Iterator method creation options.
*
* @const {GetIteratorMethodOptions} es
*/
Expand All @@ -73,17 +86,14 @@ class AggregateError<T = any> extends Error {
}

/**
* Function that returns an iterator.
* Array containing aggregated errors.
*
* @const {() => Iterator<T>} method
* @const {T[]} arr
*/
const method: () => Iterator<T> = getIteratorMethod(es, errors)

// create aggregated error list
this.errors = IterableToList(errors, method)
const arr: T[] = IterableToList(errors, getIteratorMethod(es, errors))

// re-assign errors property or throw if errors isn't iterable
CreatePropertyOrThrow(this, 'errors', IterableToList(this.errors, method))
CreatePropertyOrThrow(this, 'errors', arr)
CreateProperty(this, 'cause', options?.cause)
}
}

Expand Down
1 change: 1 addition & 0 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"extends": "./tsconfig.json",
"files": [
"./typings/es-abstract/2021/AdvanceStringIndex.d.ts",
"./typings/es-abstract/2021/CreateDataProperty.d.ts",
"./typings/es-abstract/2021/CreateDataPropertyOrThrow.d.ts",
"./typings/es-abstract/2021/GetMethod.d.ts",
"./typings/es-abstract/2021/IsArray.d.ts",
Expand Down
4 changes: 4 additions & 0 deletions typings/es-abstract/2021/CreateDataProperty.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module 'es-abstract/2021/CreateDataProperty' {
const CreateDataProperty: typeof import('es-abstract/2019/CreateDataProperty')
export default CreateDataProperty
}
8 changes: 7 additions & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ const config: UserConfigExport = defineConfig((): UserConfig => {
coverage: {
all: true,
clean: true,
exclude: ['**/__mocks__/**', '**/__tests__/**', '**/index.ts'],
exclude: [
'**/__mocks__/**',
'**/__tests__/**',
'**/index.ts',
'src/options-get-iterator-method.ts',
'src/options.ts'
],
extension: ['.ts'],
include: ['src'],
reporter: ['json-summary', 'lcov', 'text'],
Expand Down

0 comments on commit d321773

Please sign in to comment.