Skip to content

Conversation

@JonSilver
Copy link
Contributor

  • Add generic error type parameter (E = Error) to all attempt types and functions
  • Allows clients to type custom error types in addition to return values
  • Bump version from 1.3.9 to 1.4.0 (minor version bump)
  • Upgrade all dependencies to latest versions:
    • ESLint 8 → 9 (migrated to flat config)
    • TypeScript 5.3 → 5.9
    • Vite 5 → 7
    • Vitest 0.34 → 4.0
    • Prettier 2 → 3
    • All other dev dependencies to latest
  • Fix package.json exports order (types before import/require)
  • All tests passing with new type system

- Add generic error type parameter (E = Error) to all attempt types and functions
- Allows clients to type custom error types in addition to return values
- Bump version from 1.3.9 to 1.4.0 (minor version bump)
- Upgrade all dependencies to latest versions:
  - ESLint 8 → 9 (migrated to flat config)
  - TypeScript 5.3 → 5.9
  - Vite 5 → 7
  - Vitest 0.34 → 4.0
  - Prettier 2 → 3
  - All other dev dependencies to latest
- Fix package.json exports order (types before import/require)
- All tests passing with new type system
- Document new v1.4.0 feature for typing custom error types
- Add TypeScript usage examples for both sync and async
- Show custom error type patterns (ValidationError, ApiError)
- Clarify backward compatibility (defaults to Error type)
- Add sideEffects: false for better tree-shaking
- Add engines constraint (Node.js >= 18.0.0)
- Document v1.4.0 changes including error generic feature
- List all dependency upgrades and build tooling changes
- Follow Keep a Changelog format for consistency
- Add CI workflow for automated testing on PRs and pushes
  - Test on Node.js 18.x, 20.x, and 22.x
  - Run linting, tests, and build
  - Upload code coverage to Codecov
- Add publish workflow for automated npm releases
  - Triggers on GitHub releases
  - Publishes to npm with provenance
  - Requires NPM_TOKEN secret to be configured
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds generic error type parameters to the attempt library, enabling TypeScript users to specify custom error types in addition to result types. It also includes a major upgrade of all development dependencies to their latest versions, including ESLint 9 with flat config, TypeScript 5.9, Vite 7, and Vitest 4.

Key Changes:

  • Added generic error type parameter <T, E = Error> to all attempt types and functions with backward compatibility
  • Upgraded tooling dependencies: ESLint 8→9 (flat config), TypeScript 5.3→5.9, Vite 5→7, Vitest 0.34→4
  • Added comprehensive TypeScript usage documentation and examples in ReadMe.md

Reviewed Changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/index.ts Added generic error type parameter E to all types and functions, enabling custom error type support
package.json Bumped version to 1.4.0, reordered exports (types first), added sideEffects and engines fields, upgraded all dependencies
eslint.config.js New flat config format for ESLint 9 migration
ReadMe.md Added comprehensive TypeScript documentation with examples for generic error types
CHANGELOG.md New changelog documenting v1.4.0 changes and upgrade details
.github/workflows/publish.yml New GitHub Actions workflow for automated npm publishing
.github/workflows/ci.yml New GitHub Actions workflow for continuous integration testing
.eslintrc.json Removed legacy ESLint configuration file (replaced by flat config)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

checkFn(fn);

return Promise.resolve().then(fn).then(success).catch(fail) as Promise<AttemptReturnTuple<T>>;
return Promise.resolve().then(fn).then(success).catch(fail) as Promise<AttemptReturnTuple<T, E>>;
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type inference for success and fail callbacks in the promise chain may not correctly infer the generic error type E. When calling .then(success).catch(fail), TypeScript cannot automatically infer the E type parameter for these generic functions, which could lead to type safety issues.

Consider explicitly passing the type parameters or creating bound versions of these functions within the attemptPromise function:

export const attemptPromise = <T, E = Error>(fn: () => Promise<T>): Promise<AttemptReturnTuple<T, E>> => {
    checkFn(fn);
    
    return Promise.resolve()
        .then(fn)
        .then((r: T) => success<T, E>(r))
        .catch((e: any) => fail<E>(e as E)) as Promise<AttemptReturnTuple<T, E>>;
};

This ensures the generic error type E is properly threaded through the promise chain.

Suggested change
return Promise.resolve().then(fn).then(success).catch(fail) as Promise<AttemptReturnTuple<T, E>>;
return Promise.resolve()
.then(fn)
.then((r: T) => success<T, E>(r))
.catch((e: any) => fail<E>(e as E)) as Promise<AttemptReturnTuple<T, E>>;

Copilot uses AI. Check for mistakes.
Comment on lines +24 to 32
export const attempt = <T, E = Error>(fn: () => T): AttemptReturnTuple<T, E> => {
checkFn(fn);

try {
return success(fn());
} catch (e) {
return fail(e as Error);
return fail(e as E);
}
};
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new generic error type parameter feature (the main feature of v1.4.0) lacks test coverage. The existing tests don't verify that custom error types work correctly with the new E type parameter.

Consider adding tests that:

  1. Use custom error types with both attempt and attemptPromise
  2. Verify that the error type is correctly inferred/enforced
  3. Test that the default Error type still works (backward compatibility)

Example test:

it("should handle custom error types", () => {
    class ValidationError extends Error {
        code: number;
        constructor(message: string, code: number) {
            super(message);
            this.code = code;
        }
    }
    
    const [err, result] = attempt<string, ValidationError>(() => {
        throw new ValidationError("Invalid input", 400);
    });
    
    expect(err).toBeInstanceOf(ValidationError);
    expect(err?.code).toBe(400);
    expect(result).toBeUndefined();
});

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +65
const [error, result] = attempt<User, ValidationError>(() => {
// your code here
});

// Now TypeScript knows:
// - error is ValidationError | undefined
// - result is User | undefined
```
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation example references an undefined type User. The example should either define this type or use a simpler example that doesn't require additional type definitions.

Suggested fix:

// Type both result and error
class ValidationError extends Error {
    code: number;
}

const [error, result] = attempt<string, ValidationError>(() => {
    // your code here
    return "success";
});

// Now TypeScript knows:
// - error is ValidationError | undefined
// - result is string | undefined

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +81
const [error, data] = await attemptPromise<ApiResponse, ApiError>(
async () => await fetchData()
);

if (error) {
// error is typed as ApiError
console.error(error.statusCode, error.message);
} else {
// data is typed as ApiResponse
console.log(data.results);
}
```
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation example references undefined types ApiResponse, ApiError, and fetchData. The example should either define these types/functions or use a simpler, self-contained example.

Suggested fix:

class ApiError extends Error {
    statusCode: number;
    constructor(message: string, statusCode: number) {
        super(message);
        this.statusCode = statusCode;
    }
}

interface ApiResponse {
    results: string[];
}

const [error, data] = await attemptPromise<ApiResponse, ApiError>(
    async () => {
        const response = await fetch('/api/data');
        if (!response.ok) {
            throw new ApiError('API request failed', response.status);
        }
        return response.json();
    }
);

if (error) {
    // error is typed as ApiError
    console.error(error.statusCode, error.message);
} else {
    // data is typed as ApiResponse
    console.log(data.results);
}

Copilot uses AI. Check for mistakes.
],
"sideEffects": false,
"engines": {
"node": ">=18.0.0"
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The engines field specifies node: ">=18.0.0", but Vite 7.2.2 requires Node.js ^20.19.0 || >=22.12.0 (as seen in npm-shrinkwrap.json). This creates a conflict where the package claims to support Node.js 18.x, but the build tooling (Vite) requires at least Node.js 20.19.0.

Update the engines field to match Vite 7's requirements:

"engines": {
    "node": "^20.19.0 || >=22.12.0"
}

Alternatively, if Node.js 18 support is required, consider downgrading Vite to version 6.x which supports Node.js 18.

Suggested change
"node": ">=18.0.0"
"node": "^20.19.0 || >=22.12.0"

Copilot uses AI. Check for mistakes.

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI workflow tests against Node.js 18.x, but Vite 7.2.2 (used in the build) requires Node.js ^20.19.0 || >=22.12.0. This means the CI build step will fail on Node.js 18.x.

Update the matrix to only include Node.js versions compatible with Vite 7:

matrix:
    node-version: [20.x, 22.x]

Or add a separate job that tests on Node 18 but skips the build step, if runtime compatibility with Node 18 is desired.

Suggested change
node-version: [18.x, 20.x, 22.x]
node-version: [20.x, 22.x]

Copilot uses AI. Check for mistakes.
- Example: `attempt<User, ValidationError>(() => ...)`
- Package metadata:
- `sideEffects: false` for better tree-shaking support
- `engines` field specifying Node.js >= 18.0.0
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog incorrectly states that the engines field specifies "Node.js >= 18.0.0". However, Vite 7.2.2 (one of the upgraded dependencies) requires Node.js ^20.19.0 || >=22.12.0, which means Node.js 18.x is not actually supported.

Update the changelog to reflect the actual Node.js version requirement, or adjust the dependencies to support Node.js 18 if that's the intended target.

Suggested change
- `engines` field specifying Node.js >= 18.0.0
- `engines` field specifying Node.js ^20.19.0 || >=22.12.0

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants