From 5442fb58c6a648ab3d00943ac975c7c0b0f7be29 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 12:22:31 -0300 Subject: [PATCH 01/14] chore: Bump version to 1.5.3 in package.json --- package/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/package.json b/package/package.json index c691de1..24d0260 100644 --- a/package/package.json +++ b/package/package.json @@ -1,6 +1,6 @@ { "name": "tryless", - "version": "1.5.2", + "version": "1.5.3", "description": "A simple Result type for TypeScript. Inspired by Rust's Result type.", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", From 0558ca1a4e833fbaea8a40b92fd81266f0aa46f1 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 12:26:01 -0300 Subject: [PATCH 02/14] refactor: Update type annotations in unwrap-error tests for improved type safety --- package/e2e/browser/unwrap-error.spec.ts | 2 +- package/e2e/node/unwrap-error.spec.ts | 4 ++-- package/e2e/shared/api-consistency.spec.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package/e2e/browser/unwrap-error.spec.ts b/package/e2e/browser/unwrap-error.spec.ts index bf76a11..0f9a892 100644 --- a/package/e2e/browser/unwrap-error.spec.ts +++ b/package/e2e/browser/unwrap-error.spec.ts @@ -19,7 +19,7 @@ describe('UnwrapError in Browser', () => { }); it('should handle non-JSON-serializable reasons', () => { - const circular: any = { name: 'test' }; + const circular: Record = { name: 'test' }; circular.self = circular; const result = err('CircularError', circular); diff --git a/package/e2e/node/unwrap-error.spec.ts b/package/e2e/node/unwrap-error.spec.ts index eaf23a6..6bf4c8a 100644 --- a/package/e2e/node/unwrap-error.spec.ts +++ b/package/e2e/node/unwrap-error.spec.ts @@ -33,7 +33,7 @@ describe('UnwrapError in Node.js', () => { result.unwrap(); } catch (error) { if (error instanceof UnwrapError) { - const customInspect = (error as any)[ + const customInspect = (error as Record)[ Symbol.for('nodejs.util.inspect.custom') ]; expect(typeof customInspect).toBe('function'); @@ -42,7 +42,7 @@ describe('UnwrapError in Node.js', () => { }); it('should handle circular references gracefully', () => { - const circular: any = { name: 'test' }; + const circular: Record = { name: 'test' }; circular.self = circular; const result = err('CircularError', circular); diff --git a/package/e2e/shared/api-consistency.spec.ts b/package/e2e/shared/api-consistency.spec.ts index bb29a63..6810e2f 100644 --- a/package/e2e/shared/api-consistency.spec.ts +++ b/package/e2e/shared/api-consistency.spec.ts @@ -122,7 +122,7 @@ describe('Result methods consistency', () => { it('should pass through Err values', () => { const result = err('TestError', 'Test reason'); - const mapped = result.andThen((data) => ok((data as any) * 2)); + const mapped = result.andThen((data) => ok((data as unknown) * 2)); expect(mapped.success).toBe(false); if (!mapped.success) { From a8303e78289ee0f7470d5fc80d1f472553d28571 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 12:31:49 -0300 Subject: [PATCH 03/14] chore: Add Playwright browser installation step to CI workflow for enhanced testing capabilities --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb2267c..6bd2b9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps chromium + - name: Run linter run: pnpm --filter tryless lint From ab80fe7d69b7b6930644465216176f53fb252482 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 12:34:36 -0300 Subject: [PATCH 04/14] chore: Update Playwright browser installation command in CI workflow to use filter for improved dependency management --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bd2b9e..d6c56c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Install Playwright browsers - run: pnpm exec playwright install --with-deps chromium + run: pnpm --filter tryless exec playwright install --with-deps chromium - name: Run linter run: pnpm --filter tryless lint From 55108b9f410ea4ebbae47a4cf2f84ba4efc80304 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 12:36:27 -0300 Subject: [PATCH 05/14] chore: Reorder test execution in CI workflow for improved clarity and consistency --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6c56c3..b32de51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,12 +42,12 @@ jobs: - name: Run linter run: pnpm --filter tryless lint - - name: Run tests - run: pnpm --filter tryless test - - name: Build package run: pnpm --filter tryless build + - name: Run tests + run: pnpm --filter tryless test + - name: Check types run: pnpm --filter tryless exec tsc --noEmit From 0c7d9361235b5c6e82c24dad9366c4bb2f2eed60 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 12:48:50 -0300 Subject: [PATCH 06/14] chore: Enhance CI/CD workflows with validation steps, multi-version testing, and improved Playwright caching --- .github/workflows/ci.yml | 102 ++++++++++++++++++++++++++++++++-- .github/workflows/publish.yml | 80 +++++++++++++++++++++++++- CHANGELOG.md | 32 +++++++++-- 3 files changed, 202 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b32de51..b057ea3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,12 +11,68 @@ on: - dev jobs: + # Validações rápidas primeiro + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.18.3 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run linter + run: pnpm --filter tryless lint + + # Verificação de tipos + typecheck: + name: Type Check + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.18.3 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check types + run: pnpm --filter tryless exec tsc --noEmit + + # Build e testes em múltiplas versões do Node test: + name: Test (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest + needs: [lint, typecheck] strategy: + fail-fast: false matrix: - node-version: [22] + node-version: [18, 20, 22] steps: - name: Checkout repository @@ -36,11 +92,24 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Get Playwright version + id: playwright-version + run: echo "version=$(pnpm --filter tryless list @playwright/test --depth=0 --json | jq -r '.[0].version')" >> $GITHUB_OUTPUT + + - name: Cache Playwright browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' run: pnpm --filter tryless exec playwright install --with-deps chromium - - name: Run linter - run: pnpm --filter tryless lint + - name: Install Playwright system dependencies + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: pnpm --filter tryless exec playwright install-deps chromium - name: Build package run: pnpm --filter tryless build @@ -48,6 +117,29 @@ jobs: - name: Run tests run: pnpm --filter tryless test - - name: Check types - run: pnpm --filter tryless exec tsc --noEmit + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-results-node-${{ matrix.node-version }} + path: package/test-results/ + retention-days: 7 + + # Job final que depende de todos os outros + ci-success: + name: CI Success + runs-on: ubuntu-latest + needs: [lint, typecheck, test] + if: always() + + steps: + - name: Check all jobs + run: | + if [[ "${{ needs.lint.result }}" != "success" ]] || \ + [[ "${{ needs.typecheck.result }}" != "success" ]] || \ + [[ "${{ needs.test.result }}" != "success" ]]; then + echo "One or more jobs failed" + exit 1 + fi + echo "All jobs passed successfully!" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a846786..bed7f64 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,6 +16,22 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Validate tag format + id: tag-info + run: | + TAG=${GITHUB_REF#refs/tags/} + echo "tag=$TAG" >> $GITHUB_OUTPUT + + # Remove 'v' prefix for version comparison + VERSION=${TAG#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Validate semantic versioning format + if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$ ]]; then + echo "Error: Tag must follow semantic versioning (e.g., v1.0.0)" + exit 1 + fi + - name: Setup pnpm uses: pnpm/action-setup@v4 with: @@ -31,18 +47,76 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Verify package.json version matches tag + run: | + PACKAGE_VERSION=$(node -p "require('./package/package.json').version") + TAG_VERSION="${{ steps.tag-info.outputs.version }}" + + if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then + echo "Error: package.json version ($PACKAGE_VERSION) does not match tag version ($TAG_VERSION)" + exit 1 + fi + echo "✅ Version validation passed: $PACKAGE_VERSION" + + - name: Verify CHANGELOG entry exists + run: | + TAG_VERSION="${{ steps.tag-info.outputs.version }}" + + if ! grep -q "## \[$TAG_VERSION\]" CHANGELOG.md && ! grep -q "## $TAG_VERSION" CHANGELOG.md; then + echo "⚠️ Warning: No CHANGELOG entry found for version $TAG_VERSION" + echo "Please update CHANGELOG.md before publishing" + exit 1 + fi + echo "✅ CHANGELOG entry found for version $TAG_VERSION" + - name: Run linter run: pnpm --filter tryless lint - - name: Run tests - run: pnpm --filter tryless test + - name: Check types + run: pnpm --filter tryless exec tsc --noEmit + + - name: Get Playwright version + id: playwright-version + run: echo "version=$(pnpm --filter tryless list @playwright/test --depth=0 --json | jq -r '.[0].version')" >> $GITHUB_OUTPUT + + - name: Cache Playwright browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: pnpm --filter tryless exec playwright install --with-deps chromium + + - name: Install Playwright system dependencies + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: pnpm --filter tryless exec playwright install-deps chromium - name: Build package run: pnpm --filter tryless build + - name: Run tests + run: pnpm --filter tryless test + - name: Copy README to package run: cp README.md package/README.md + - name: Verify package contents + working-directory: ./package + run: | + echo "📦 Package contents:" + ls -la + echo "" + echo "📄 Dist files:" + ls -la dist/ + echo "" + if [ ! -f "README.md" ]; then + echo "Error: README.md not found in package directory" + exit 1 + fi + - name: Publish to NPM working-directory: ./package run: npm publish --provenance --access public @@ -61,7 +135,7 @@ jobs: ## 📦 Installation ```bash - npm install tryless@${{ github.ref_name }} + npm install tryless@${{ steps.tag-info.outputs.version }} ``` ## 📚 Documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9507f..e2665fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.5.3] - 2025-10-27 ### Added -- Comprehensive documentation in README -- GitHub Actions workflows for CI/CD -- Publishing guide for contributors +- `IResult` type for type-safe result handling with success and error types +- Runtime module for Node.js environment detection +- Custom inspect function for `UnwrapError` with Node.js support +- Tests for `IUnknownError`, `IUnknownOk`, `IUnknownErr`, and `IUnknownOkErr` type utilities +- TypeScript configuration for test types with type-checking capabilities +- Comprehensive test script in package.json covering unit, integration, and type tests +- Version validation in publish workflow (ensures package.json matches git tag) +- CHANGELOG entry verification before publishing +- Type checking step in publish workflow +- Automated test results upload on failure for easier debugging +- CI success job for better branch protection integration +- Playwright browser caching in both CI and publish workflows +- Playwright browser installation step in CI workflow + +### Changed +- Migrated testing framework from Jest to Vitest for better performance and DX +- Optimized CI workflow with parallel job execution (lint, typecheck, and tests run simultaneously) +- Added multi-version Node.js testing (Node 18, 20, and 22) to ensure compatibility across LTS versions +- Implemented intelligent Playwright browser cache to reduce CI execution time +- Enhanced publish workflow with comprehensive validation steps +- Reordered test execution in CI workflow for improved clarity and consistency +- Updated Playwright browser installation to use pnpm filter for better dependency management +- Configured Turbo for test dependencies + +### Fixed +- Updated Vitest configuration to include specific test directories for better test organization +- Improved type annotations in unwrap-error tests for enhanced type safety ## [1.4.3] - 2024-XX-XX From d73770a4e98977237c70e1f03137a1c9232bd002 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 12:53:31 -0300 Subject: [PATCH 07/14] chore: Remove Node.js version 18 from CI workflow matrix for streamlined testing --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b057ea3..ce348ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18, 20, 22] + node-version: [20, 22] steps: - name: Checkout repository From 3182f656002caac8e893f4e452e476333d5b5fdb Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 13:28:34 -0300 Subject: [PATCH 08/14] chore: Update keywords in package.json for improved clarity and relevance in error handling and functional programming --- package/package.json | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/package/package.json b/package/package.json index 24d0260..a3b70a7 100644 --- a/package/package.json +++ b/package/package.json @@ -68,10 +68,24 @@ }, "keywords": [ "result", - "error", + "result-type", "error-handling", - "typed-error", - "rust", - "typescript" + "type-safe", + "typescript", + "rust-inspired", + "functional-programming", + "railway-oriented", + "async-error-handling", + "promise", + "no-throw", + "either", + "option", + "monad", + "error-management", + "exception-handling", + "fp", + "functional", + "nodejs", + "zero-dependencies" ] } \ No newline at end of file From 86e6fb7ede5777d8a12988b77d187b9c8c9d7bde Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 13:35:01 -0300 Subject: [PATCH 09/14] chore: Bump version to 1.5.4 and enhance package metadata for improved discoverability and SEO --- CHANGELOG.md | 13 ++++++++++++- package/package.json | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2665fa..e6b86af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.4] - 2025-10-27 + +### Changed +- Enhanced NPM keywords for better package discoverability and SEO optimization +- Added strategic keywords: `result-type`, `type-safe`, `functional-programming`, `railway-oriented`, `async-error-handling`, `promise`, `no-throw`, `either`, `option`, `monad`, `error-management`, `exception-handling`, `fp`, `functional`, `nodejs`, `zero-dependencies` + +### Documentation +- Improved package metadata to increase visibility in NPM search results + ## [1.5.3] - 2025-10-27 ### Added @@ -56,6 +65,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Examples for common use cases - Integration examples with Zod validation -[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.4.3...HEAD +[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.4...HEAD +[1.5.4]: https://github.com/jordyfontoura/tryless/compare/v1.5.3...v1.5.4 +[1.5.3]: https://github.com/jordyfontoura/tryless/compare/v1.4.3...v1.5.3 [1.4.3]: https://github.com/jordyfontoura/tryless/releases/tag/v1.4.3 diff --git a/package/package.json b/package/package.json index a3b70a7..bd53bcb 100644 --- a/package/package.json +++ b/package/package.json @@ -1,7 +1,7 @@ { "name": "tryless", - "version": "1.5.3", - "description": "A simple Result type for TypeScript. Inspired by Rust's Result type.", + "version": "1.5.4", + "description": "Type-safe error handling for TypeScript without try-catch hell", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "types": "dist/index.d.ts", From 1e94241d30590c7edd496b4dafecb7b635c67c05 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Mon, 27 Oct 2025 14:08:22 -0300 Subject: [PATCH 10/14] chore: Bump version to 1.5.5 and migrate documentation examples to use `resultfy()` for improved readability and best practices --- CHANGELOG.md | 19 +++++++- README.md | 90 ++++++++++++++++++++++--------------- package/package.json | 2 +- package/src/helpers.ts | 56 +++++++++++++---------- package/src/result/types.ts | 14 +++--- 5 files changed, 112 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6b86af..a7976ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.5] - 2025-10-27 + +### Changed +- Migrated documentation examples from `.then(ok, errReject())` pattern to `resultfy()` for improved readability + +### Documentation +- Refactored all promise handling examples to use `resultfy()` as the recommended approach +- Added comparison highlighting benefits of `resultfy()` over `.then(ok, errReject())` +- Enhanced `resultfy` section with best practices and clearer examples +- Maintained `errReject` documentation for complex promise chain scenarios +- Updated JSDoc docstrings in source code: + - `resultfy`: Marked as "Recommended approach" with improved examples showing custom error messages + - `errReject`: Added note to prefer `resultfy()` for simple cases, with comparison examples + - `IResult` type: Updated example to use `resultfy()` instead of try-catch pattern + ## [1.5.4] - 2025-10-27 ### Changed - Enhanced NPM keywords for better package discoverability and SEO optimization - Added strategic keywords: `result-type`, `type-safe`, `functional-programming`, `railway-oriented`, `async-error-handling`, `promise`, `no-throw`, `either`, `option`, `monad`, `error-management`, `exception-handling`, `fp`, `functional`, `nodejs`, `zero-dependencies` +- Updated package description from generic to more compelling: "Type-safe error handling for TypeScript without try-catch hell" ### Documentation - Improved package metadata to increase visibility in NPM search results @@ -65,7 +81,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Examples for common use cases - Integration examples with Zod validation -[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.4...HEAD +[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.5...HEAD +[1.5.5]: https://github.com/jordyfontoura/tryless/compare/v1.5.4...v1.5.5 [1.5.4]: https://github.com/jordyfontoura/tryless/compare/v1.5.3...v1.5.4 [1.5.3]: https://github.com/jordyfontoura/tryless/compare/v1.4.3...v1.5.3 [1.4.3]: https://github.com/jordyfontoura/tryless/releases/tag/v1.4.3 diff --git a/README.md b/README.md index e5a48e2..f931507 100644 --- a/README.md +++ b/README.md @@ -39,15 +39,19 @@ async function fetchUser(id: string) { **After (clean & type-safe):** ```typescript -import { ok, err, errReject } from 'tryless'; +import { ok, err, resultfy } from 'tryless'; async function fetchUser(id: string) { - const responseResult = await fetch(`/api/users/${id}`) - .then(ok, errReject('fetch-failed')); + const responseResult = await resultfy( + fetch(`/api/users/${id}`), + 'fetch-failed' + ); if (!responseResult.success) return responseResult; - const jsonResult = await responseResult.data.json() - .then(ok, errReject('invalid-json')); + const jsonResult = await resultfy( + responseResult.data.json(), + 'invalid-json' + ); if (!jsonResult.success) return jsonResult; const userResult = userSchema.safeParse(jsonResult.data); @@ -106,12 +110,14 @@ if (result.success) { ### With Promises ```typescript -import { ok, errReject } from 'tryless'; +import { resultfy } from 'tryless'; async function getUser(id: string) { // Convert promise rejection to error result - const result = await fetch(`/api/users/${id}`) - .then(ok, errReject('user-fetch-failed')); + const result = await resultfy( + fetch(`/api/users/${id}`), + 'user-fetch-failed' + ); if (!result.success) { return result; // { success: false, error: 'user-fetch-failed', reason: ... } @@ -188,11 +194,22 @@ if (result.success) { ### resultfy -Wrap functions or promises to always return Results instead of throwing: +**Recommended approach** for wrapping promises and functions to always return Results instead of throwing: ```typescript import { resultfy } from 'tryless'; +// Wrap a promise with custom error message (most common use case) +const userResult = await resultfy( + fetch('/api/user').then(r => r.json()), + 'fetch-error' +); + +if (!userResult.success) { + console.log(userResult.error); // 'fetch-error' + console.log(userResult.reason); // Original error details +} + // Wrap a function const safeDivide = resultfy((a: number, b: number) => { if (b === 0) throw new Error('Division by zero'); @@ -204,37 +221,29 @@ const result = safeDivide(10, 2); const errorResult = safeDivide(10, 0); // { success: false, error: 'unknown', reason: Error('Division by zero') } - -// Wrap a promise -const safeUser = await resultfy( - fetch('/api/user').then(r => r.json()) -); ``` -With custom error messages: - -```typescript -const safeFetch = resultfy( - fetch('/api/data').then(r => r.json()), - 'fetch-error' -); - -if (!safeFetch.success) { - console.log(safeFetch.error); // 'fetch-error' -} -``` +**Why prefer `resultfy` over `.then(ok, errReject())`?** +- ✅ More concise and readable +- ✅ Works with any promise or function +- ✅ Custom error messages built-in +- ✅ Consistent API ### errReject Perfect for promise chains - converts rejections to error results: ```typescript -import { ok, errReject } from 'tryless'; +import { ok, errReject, resultfy } from 'tryless'; +// Still useful for complex promise chains const result = await fetch('/api/data') .then(ok, errReject('network-error')) .then(res => res.success ? res.data.json() : res) .then(ok, errReject('parse-error')); + +// Or use resultfy for simpler cases +const result = await resultfy(fetch('/api/data'), 'network-error'); ``` ### okFulfilled @@ -305,7 +314,7 @@ const value = getPrice(item).unwrapOrElse(error => { ### API Request with Validation ```typescript -import { ok, err, errReject } from 'tryless'; +import { ok, err, resultfy } from 'tryless'; import { z } from 'zod'; const userSchema = z.object({ @@ -316,8 +325,10 @@ const userSchema = z.object({ async function fetchAndValidateUser(id: string) { // Fetch data - const fetchResult = await fetch(`/api/users/${id}`) - .then(ok, errReject('network-error')); + const fetchResult = await resultfy( + fetch(`/api/users/${id}`), + 'network-error' + ); if (!fetchResult.success) return fetchResult; // Check response status @@ -327,8 +338,10 @@ async function fetchAndValidateUser(id: string) { } // Parse JSON - const jsonResult = await response.json() - .then(ok, errReject('json-parse-error')); + const jsonResult = await resultfy( + response.json(), + 'json-parse-error' + ); if (!jsonResult.success) return jsonResult; // Validate schema @@ -390,11 +403,13 @@ async function createUser(data: UserInput) { ```typescript import { readFile } from 'fs/promises'; -import { ok, errReject } from 'tryless'; +import { ok, err, resultfy } from 'tryless'; async function loadConfig(path: string) { - const fileResult = await readFile(path, 'utf-8') - .then(ok, errReject('file-read-error')); + const fileResult = await resultfy( + readFile(path, 'utf-8'), + 'file-read-error' + ); if (!fileResult.success) return fileResult; try { @@ -438,10 +453,13 @@ resultfy(promise, 'custom-error') ``` #### `errReject(error)` -Converts promise rejections to Err results. +Converts promise rejections to Err results. Useful for complex promise chains. ```typescript promise.then(ok, errReject('fetch-failed')) + +// For simpler cases, prefer resultfy: +resultfy(promise, 'fetch-failed') ``` #### `okFulfilled(mapFn)` diff --git a/package/package.json b/package/package.json index bd53bcb..e389821 100644 --- a/package/package.json +++ b/package/package.json @@ -1,6 +1,6 @@ { "name": "tryless", - "version": "1.5.4", + "version": "1.5.5", "description": "Type-safe error handling for TypeScript without try-catch hell", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", diff --git a/package/src/helpers.ts b/package/src/helpers.ts index b64384c..1a67245 100644 --- a/package/src/helpers.ts +++ b/package/src/helpers.ts @@ -39,7 +39,9 @@ export function okFulfilled(map: (data: T) => R): (data: T) => Ok { /** * Creates a curried error factory that wraps rejection reasons in error results. - * Particularly useful for converting promise rejections to error results. + * Particularly useful for converting promise rejections in complex chains. + * + * **Note:** For simple promise wrapping, prefer `resultfy()` which is more concise. * * @template E - String literal type for the error identifier * @param error - The error identifier to use for all rejections @@ -47,21 +49,24 @@ export function okFulfilled(map: (data: T) => R): (data: T) => Ok { * * @example * ```ts - * import { errReject, ok } from 'tryless'; + * import { errReject, ok, resultfy } from 'tryless'; * * // Basic usage * const onReject = errReject('FetchError'); * const result = onReject('Network down'); * // { success: false, error: 'FetchError', reason: 'Network down' } * - * // Use in promise chains to convert rejections + * // Use in complex promise chains * const userResult = await fetch('https://api.example.com/user') - * .then(ok, errReject('user:fetch-error')); - * - * if (!userResult.success) { - * console.log(userResult.error); // 'user:fetch-error' - * console.log(userResult.reason); // rejection reason - * } + * .then(ok, errReject('user:fetch-error')) + * .then(res => res.success ? res.data.json() : res) + * .then(ok, errReject('user:parse-error')); + * + * // For simpler cases, prefer resultfy: + * const userResult = await resultfy( + * fetch('https://api.example.com/user'), + * 'user:fetch-error' + * ); * ``` */ export function errReject(error: E): (reason: unknown) => Err { @@ -74,43 +79,46 @@ export function errReject(error: E): (reason: unknown) => Err< * Wraps a function or promise to always return a result instead of throwing. * Converts both synchronous throws and promise rejections into error results. * + * **Recommended approach** for wrapping promises and functions. More concise than `.then(ok, errReject())`. + * * **For Promises:** Wraps a promise to return Ok on fulfillment or Err on rejection. * * **For Functions:** Returns a wrapped version that catches errors and returns results. * * @template F - Type of the function or promise to wrap * @param fn - The function or promise to wrap + * @param error - Optional custom error identifier (defaults to 'unknown') * @returns Wrapped version that returns Result types * * @example * ```ts * import { resultfy } from 'tryless'; * + * // Wrap a promise with custom error message (most common use case) + * const userResult = await resultfy( + * fetch('https://api.example.com/user').then(r => r.json()), + * 'user:fetch-error' + * ); + * + * if (userResult.success) { + * console.log(userResult.data); // user data + * } else { + * console.log(userResult.error); // 'user:fetch-error' + * console.log(userResult.reason); // rejection reason + * } + * * // Wrap a synchronous function * const divide = (a: number, b: number) => { * if (b === 0) throw new Error('Division by zero'); * return a / b; * }; * - * const safeDivide = resultfy(divide); + * const safeDivide = resultfy(divide, 'division-error'); * const result1 = safeDivide(10, 2); * // { success: true, data: 5 } * * const result2 = safeDivide(10, 0); - * // { success: false, error: 'unknown', reason: Error('Division by zero') } - * - * // Wrap a promise - * const fetchUser = resultfy( - * fetch('https://api.example.com/user').then(r => r.json()) - * ); - * - * const userResult = await fetchUser; - * if (userResult.success) { - * console.log(userResult.data); // user data - * } else { - * console.log(userResult.error); // 'unknown' - * console.log(userResult.reason); // rejection reason - * } + * // { success: false, error: 'division-error', reason: Error('Division by zero') } * ``` */ /** diff --git a/package/src/result/types.ts b/package/src/result/types.ts index f5c159f..ca9ec72 100644 --- a/package/src/result/types.ts +++ b/package/src/result/types.ts @@ -80,15 +80,15 @@ export type IUnknownOkErr = IUnknownOk | IUnknownErr; * @example * ```ts * // Using IUnknownErr for generic error handling + * import { resultfy } from 'tryless'; + * * type GenericResult = IResult; * - * function fetchData(url: string): GenericResult { - * try { - * const data = fetch(url); - * return ok(data); - * } catch (error) { - * return err('FetchError', error); - * } + * async function fetchData(url: string): Promise { + * return resultfy( + * fetch(url).then(r => r.json()), + * 'FetchError' + * ); * } * ``` */ From 709f603960882e0fa62419472e0ec34e6dfc738e Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Tue, 3 Feb 2026 12:28:04 -0300 Subject: [PATCH 11/14] chore: Bump version to 1.5.6 and enhance `resultfy()` to support PromiseLike objects with improved type guard and test coverage --- CHANGELOG.md | 10 +- package/__tests__/helpers/resultfy.spec.ts | 111 +++++++++++++++++++++ package/package.json | 2 +- package/src/helpers.ts | 34 ++++++- 4 files changed, 153 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7976ae..24db819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.6] - 2025-01-XX + +### Changed +- Enhanced `resultfy()` to support PromiseLike objects (proxy promises) in addition to native Promise instances +- Replaced `instanceof Promise` checks with `isThenable()` type guard following Promise/A+ specification +- `resultfy()` now correctly handles any object that implements the `then` method, including proxy promises and custom promise-like objects + ## [1.5.5] - 2025-10-27 ### Changed @@ -81,7 +88,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Examples for common use cases - Integration examples with Zod validation -[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.5...HEAD +[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.6...HEAD +[1.5.6]: https://github.com/jordyfontoura/tryless/compare/v1.5.5...v1.5.6 [1.5.5]: https://github.com/jordyfontoura/tryless/compare/v1.5.4...v1.5.5 [1.5.4]: https://github.com/jordyfontoura/tryless/compare/v1.5.3...v1.5.4 [1.5.3]: https://github.com/jordyfontoura/tryless/compare/v1.4.3...v1.5.3 diff --git a/package/__tests__/helpers/resultfy.spec.ts b/package/__tests__/helpers/resultfy.spec.ts index fb2ce09..8f901c9 100644 --- a/package/__tests__/helpers/resultfy.spec.ts +++ b/package/__tests__/helpers/resultfy.spec.ts @@ -242,6 +242,117 @@ describe('resultfy', () => { }); }); + describe('PromiseLike support (proxy promises)', () => { + it('should wrap a PromiseLike object that resolves', async () => { + // Create a PromiseLike object that is not a native Promise + // This simulates a proxy promise or custom promise implementation + const promiseLike = { + then: (onFulfilled?: (value: number) => unknown) => { + if (onFulfilled) { + return Promise.resolve(onFulfilled(42)); + } + return Promise.resolve(42); + }, + }; + + const result = await resultfy(promiseLike); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data).toBe(42); + } + }); + + it('should wrap a PromiseLike object that rejects', async () => { + const promiseLike = { + then: ( + onFulfilled?: (value: number) => unknown, + onRejected?: (reason: string) => unknown + ) => { + if (onRejected) { + return Promise.resolve(onRejected('Rejection reason')); + } + return Promise.reject('Rejection reason'); + }, + }; + + const result = await resultfy(promiseLike); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBe('unknown'); + expect(result.reason).toBe('Rejection reason'); + } + }); + + it('should wrap a PromiseLike object with custom error', async () => { + const promiseLike = { + then: ( + onFulfilled?: (value: number) => unknown, + onRejected?: (reason: string) => unknown + ) => { + if (onRejected) { + return Promise.resolve(onRejected('Rejection reason')); + } + return Promise.reject('Rejection reason'); + }, + }; + + const result = await resultfy(promiseLike, 'custom-error'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBe('custom-error'); + expect(result.reason).toBe('Rejection reason'); + } + }); + + it('should handle PromiseLike returned from a function', async () => { + const createPromiseLike = (value: number) => ({ + then: (onFulfilled?: (value: number) => unknown) => { + if (onFulfilled) { + return Promise.resolve(onFulfilled(value)); + } + return Promise.resolve(value); + }, + }); + + const safeCreatePromiseLike = resultfy(createPromiseLike); + const result = await safeCreatePromiseLike(100); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data).toBe(100); + } + }); + + it('should handle PromiseLike that rejects from a function', async () => { + const createRejectingPromiseLike = () => ({ + then: ( + onFulfilled?: (value: number) => unknown, + onRejected?: (reason: string) => unknown + ) => { + if (onRejected) { + return Promise.resolve(onRejected('Function rejection')); + } + return Promise.reject('Function rejection'); + }, + }); + + const safeCreateRejectingPromiseLike = resultfy( + createRejectingPromiseLike, + 'function-error' + ); + const result = await safeCreateRejectingPromiseLike(); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBe('function-error'); + expect(result.reason).toBe('Function rejection'); + } + }); + }); + describe('function parameters preservation', () => { it('should preserve function parameters', () => { const multiply = (a: number, b: number, c: number) => a * b * c; diff --git a/package/package.json b/package/package.json index e389821..45776e5 100644 --- a/package/package.json +++ b/package/package.json @@ -1,6 +1,6 @@ { "name": "tryless", - "version": "1.5.5", + "version": "1.5.6", "description": "Type-safe error handling for TypeScript without try-catch hell", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", diff --git a/package/src/helpers.ts b/package/src/helpers.ts index 1a67245..374baa8 100644 --- a/package/src/helpers.ts +++ b/package/src/helpers.ts @@ -4,6 +4,36 @@ import { Err, Ok } from './result/classes'; import { ok } from './result/functions'; import type { IUnknownError } from './result/types'; +/** + * Type guard to check if a value is a PromiseLike object (thenable). + * Follows the Promise/A+ specification: checks if value is not null and has a then method. + * + * @template T - Type of the resolved value + * @param v - Value to check + * @returns True if the value is a PromiseLike object + * + * @example + * ```ts + * const promise = Promise.resolve(42); + * if (isThenable(promise)) { + * // TypeScript knows promise is PromiseLike + * } + * + * // Works with proxy promises too + * const proxyPromise = { then: (resolve) => resolve(42) }; + * if (isThenable(proxyPromise)) { + * // TypeScript knows proxyPromise is PromiseLike + * } + * ``` + */ +function isThenable(v: unknown): v is PromiseLike { + return ( + v !== null && + (typeof v === "object" || typeof v === "function") && + typeof (v as any).then === "function" + ); +} + /** * Creates a curried function that transforms data and wraps it in a success result. * Useful for promise chains and function composition. @@ -190,7 +220,7 @@ export function resultfy( fnError?: E ): any { const error = fnError ?? UnknownError; - if (fn instanceof Promise) { + if (isThenable(fn)) { return fn.then(ok, errReject(error)); } @@ -201,7 +231,7 @@ export function resultfy( return (function wrapper(...args: any) { try { const result = (fn as (...args: any[]) => any)(...args); - if (result instanceof Promise) { + if (isThenable(result)) { return result.then(ok, (reason) => new Err(error, reason, wrapper)); } return ok(result); From a89d6868bd7a12afb5ea9044a90a625e44531f95 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Fri, 13 Feb 2026 13:11:00 -0300 Subject: [PATCH 12/14] chore: Simplify package.json exports structure for clearer module resolution and maintainability --- package/package.json | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/package/package.json b/package/package.json index 45776e5..9e61408 100644 --- a/package/package.json +++ b/package/package.json @@ -7,14 +7,10 @@ "types": "dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.esm.js" - }, - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.cjs.js" - } + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.js", + "require": "./dist/index.cjs.js", + "default": "./dist/index.esm.js" } }, "files": [ From 166e0fb2135e202dffe9d9f61b8b5250b260e15f Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Fri, 13 Feb 2026 13:13:38 -0300 Subject: [PATCH 13/14] chore: Bump version to 1.5.7 and fix ESM module exports configuration for improved compatibility --- CHANGELOG.md | 12 ++++++++++-- package/package.json | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24db819..9e1967c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.5.6] - 2025-01-XX +## [1.5.7] - 2025-02-13 + +### Fixed +- Fixed ESM module exports configuration in package.json to ensure proper resolution of named exports (`err`, `ok`, `resultfy`, etc.) +- Simplified exports field format for better compatibility with Node.js ESM resolution and tools like `tsx` +- Resolved issue where imports like `import { err } from 'tryless'` would fail with "does not provide an export named 'err'" + +## [1.5.6] - 2025-02-13 ### Changed - Enhanced `resultfy()` to support PromiseLike objects (proxy promises) in addition to native Promise instances @@ -88,7 +95,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Examples for common use cases - Integration examples with Zod validation -[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.6...HEAD +[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.7...HEAD +[1.5.7]: https://github.com/jordyfontoura/tryless/compare/v1.5.6...v1.5.7 [1.5.6]: https://github.com/jordyfontoura/tryless/compare/v1.5.5...v1.5.6 [1.5.5]: https://github.com/jordyfontoura/tryless/compare/v1.5.4...v1.5.5 [1.5.4]: https://github.com/jordyfontoura/tryless/compare/v1.5.3...v1.5.4 diff --git a/package/package.json b/package/package.json index 9e61408..6197154 100644 --- a/package/package.json +++ b/package/package.json @@ -1,6 +1,6 @@ { "name": "tryless", - "version": "1.5.6", + "version": "1.5.7", "description": "Type-safe error handling for TypeScript without try-catch hell", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", From 60164aaf61910d0fe8f8cbe112a2685bb39fe132 Mon Sep 17 00:00:00 2001 From: Jordy Fontoura Date: Fri, 13 Feb 2026 13:39:02 -0300 Subject: [PATCH 14/14] chore: Bump version to 1.5.8 and fix ESM module compatibility by updating output extensions and configuration --- CHANGELOG.md | 12 +++++++++++- package/e2e/browser/unwrap-error.spec.ts | 2 +- package/e2e/node/unwrap-error.spec.ts | 2 +- package/e2e/shared/api-consistency.spec.ts | 2 +- package/package.json | 8 ++++---- package/rollup.config.js | 2 +- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e1967c..cd5bc79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.8] - 2025-02-13 + +### Fixed +- Fixed ESM module compatibility issue with ESM loaders/runtimes like `tsx` by changing ESM output extension from `.js` to `.mjs` +- Updated Rollup config to generate `dist/index.mjs` instead of `dist/index.esm.js` to ensure proper ESM resolution +- Updated `package.json` exports to use `.mjs` extension for ESM imports, resolving "does not provide an export named 'err'" errors +- The `.mjs` extension is always treated as ESM by Node.js regardless of `package.json` `type` field, eliminating ambiguity +- This fix ensures compatibility with tools like `tsx`, `ts-node` with ESM loader, and other ESM loaders without requiring `"type": "module"` in package.json + ## [1.5.7] - 2025-02-13 ### Fixed @@ -95,7 +104,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Examples for common use cases - Integration examples with Zod validation -[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.7...HEAD +[Unreleased]: https://github.com/jordyfontoura/tryless/compare/v1.5.8...HEAD +[1.5.8]: https://github.com/jordyfontoura/tryless/compare/v1.5.7...v1.5.8 [1.5.7]: https://github.com/jordyfontoura/tryless/compare/v1.5.6...v1.5.7 [1.5.6]: https://github.com/jordyfontoura/tryless/compare/v1.5.5...v1.5.6 [1.5.5]: https://github.com/jordyfontoura/tryless/compare/v1.5.4...v1.5.5 diff --git a/package/e2e/browser/unwrap-error.spec.ts b/package/e2e/browser/unwrap-error.spec.ts index 0f9a892..dbecbbe 100644 --- a/package/e2e/browser/unwrap-error.spec.ts +++ b/package/e2e/browser/unwrap-error.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { err, UnwrapError } from '../../dist/index.esm.js'; +import { err, UnwrapError } from '../../dist/index.mjs'; /** * Tests for UnwrapError in browser environment diff --git a/package/e2e/node/unwrap-error.spec.ts b/package/e2e/node/unwrap-error.spec.ts index 6bf4c8a..209fded 100644 --- a/package/e2e/node/unwrap-error.spec.ts +++ b/package/e2e/node/unwrap-error.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { err, UnwrapError } from '../../dist/index.esm.js'; +import { err, UnwrapError } from '../../dist/index.mjs'; import { inspect } from 'util'; /** diff --git a/package/e2e/shared/api-consistency.spec.ts b/package/e2e/shared/api-consistency.spec.ts index 6810e2f..cafe0e6 100644 --- a/package/e2e/shared/api-consistency.spec.ts +++ b/package/e2e/shared/api-consistency.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { ok, err } from '../../dist/index.esm.js'; +import { ok, err } from '../../dist/index.mjs'; /** * Tests to ensure API consistency across Node.js and Browser environments diff --git a/package/package.json b/package/package.json index 6197154..3d3a9fb 100644 --- a/package/package.json +++ b/package/package.json @@ -1,16 +1,16 @@ { "name": "tryless", - "version": "1.5.7", + "version": "1.5.8", "description": "Type-safe error handling for TypeScript without try-catch hell", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", + "module": "dist/index.mjs", "types": "dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.esm.js", + "import": "./dist/index.mjs", "require": "./dist/index.cjs.js", - "default": "./dist/index.esm.js" + "default": "./dist/index.mjs" } }, "files": [ diff --git a/package/rollup.config.js b/package/rollup.config.js index f9f0a2b..7c255f4 100644 --- a/package/rollup.config.js +++ b/package/rollup.config.js @@ -13,7 +13,7 @@ export default [ sourcemap: true }, { - file: './dist/index.esm.js', + file: './dist/index.mjs', format: 'esm', sourcemap: true }