go-go-try
Version:
Tries to execute a sync/async function, returns a result tuple
241 lines (186 loc) • 8.32 kB
Markdown
# go-go-try - Agent Guide
## Project Overview
**go-go-try** is a TypeScript utility library for error handling inspired by Go's error handling pattern. It provides a functional approach to try/catch operations by returning a tuple `[error, value]` instead of throwing exceptions.
- **Name**: go-go-try
- **Version**: 7.2.1
- **License**: MIT
- **Repository**: thelinuxlich/go-go-try
- **Node.js Requirements**: >= 16
### Key Features
- Zero runtime dependencies
- Dual package support (CommonJS and ESM)
- Full TypeScript support with precise type inference
- Support for sync/async functions, promises, and direct values
- Parallel execution utilities with optional concurrency control
- Tagged errors for discriminated union pattern matching
## Technology Stack
- **Language**: TypeScript 5.9.3
- **Build Tool**: [pkgroll](https://github.com/privatenumber/pkgroll) - A zero-config TypeScript package bundler
- **Linter**: [Biome](https://biomejs.dev/) - Fast linter and formatter
- **Test Framework**: [Vitest](https://vitest.dev/) - Vite-native unit test framework
- **Type Testing**: [@ark/attest](https://github.com/arktypeio/arktype) - Runtime type assertions for TypeScript
- **Git Hooks**: [Husky](https://typicode.github.io/husky/) + [lint-staged](https://github.com/lint-staged/lint-staged)
## Project Structure
```
.
├── src/
│ ├── index.ts # Main source file - exports all functions and types
│ └── index.test.ts # Comprehensive test suite with runtime and type tests
├── dist/ # Build output (generated by pkgroll)
│ ├── index.cjs # CommonJS build
│ ├── index.mjs # ES Module build
│ ├── index.d.cts # CommonJS type definitions
│ └── index.d.mts # ES Module type definitions
├── .github/workflows/
│ └── main.yml # CI configuration for GitHub Actions
├── .husky/
│ └── pre-commit # Git pre-commit hook (runs lint-staged)
├── .attest/ # Ark attest cache directory
├── package.json # Package configuration with dual CJS/ESM exports
├── tsconfig.json # TypeScript strict configuration
├── vitest.config.ts # Vitest configuration with type checking and coverage
├── setupVitest.ts # Vitest global setup for @ark/attest
└── README.md # User-facing documentation
```
## Build and Test Commands
```bash
# Build the project (generates dist/ with CJS, ESM, and type definitions)
npm run build
# Run linting with auto-fix
npm run lint
# Run the full test suite (build + lint + vitest)
npm test
# Run tests with coverage report
npm run test:coverage
```
The `npm test` script is a composite that:
1. Builds the project
2. Runs the linter
3. Executes Vitest tests with type checking
## Code Style Guidelines
- **Linter**: Biome is used for linting and formatting with default configuration (no biome.json present)
- **Pre-commit**: Husky runs `lint-staged` which lints all staged `.ts` files via Biome
- **Strict TypeScript**: The `tsconfig.json` enforces strict mode with additional checks:
- `noUnusedLocals`: true
- `noUnusedParameters`: true
- `allowUnreachableCode`: false
- `noUncheckedIndexedAccess`: true
- `noFallthroughCasesInSwitch`: true
- `forceConsistentCasingInFileNames`: true
## Testing Instructions
### Test Structure
Tests are co-located with source code in `src/index.test.ts` using Vitest.
### Test Types
1. **Runtime Tests**: Standard unit tests verifying behavior using Vitest's `assert` and `test`
2. **Type Tests**: Using `@ark/attest` to verify TypeScript type inference at runtime with `attest<T>(value)`
### Key Testing Patterns
```typescript
// Runtime test
import { assert, test } from 'vitest'
test('description', () => {
const result = goTry(() => 'value')
assert.equal(result[1], 'value')
})
// Type test
import { attest } from '@ark/attest'
test('types are correct', () => {
const result = goTry('value')
attest<Result<string, string>>(result)
})
```
### Running Tests
```bash
# Run all tests with type checking
npx vitest run
# Run tests in watch mode (during development)
npx vitest
# Run with coverage
npx vitest run --coverage
```
### Coverage Configuration
Coverage is provided by `@vitest/coverage-v8` with reporters: text, json, html, and lcov. Excluded paths:
- `node_modules/`
- `dist/`
- `**/*.test.ts`
- `setupVitest.ts`
## API Design
### Core Functions
| Function | Description | Error Type |
|----------|-------------|------------|
| `goTry<T>(value)` | Returns `[string \| undefined, T \| undefined]` | Error message string |
| `goTryRaw<T, E>(value, ErrorClass?)` | Returns `[E \| undefined, T \| undefined]` | Raw Error object or tagged error |
| `goTryOr<T>(value, defaultValue)` | Returns `[string \| undefined, T]` | Error message with fallback default |
| `goTryAll<T>(items, options?)` | Parallel execution, returns `[errors[], results[]]` | Error message strings |
| `goTryAllRaw<T>(items, options?)` | Parallel execution, returns `[Error[], results[]]` | Raw Error objects |
### Input Handling
All `goTry*` functions accept:
- Direct values
- Functions (sync or async)
- Promises
### Type Helpers
- **`Result<E, T>`**: The tuple type `[E \| undefined, T \| undefined]`
- **`Success<T>`**: `[undefined, T]`
- **`Failure<E>`**: `[E, undefined]`
- **`TaggedError<T>`**: Interface for discriminated errors with `_tag` property
- **`TaggedUnion<T>`**: Creates union type from multiple tagged error classes
- **`isSuccess(result)`**: Type guard to check if result is success
- **`isFailure(result)`**: Type guard to check if result is failure
- **`success(value)`**: Helper to create Success tuple
- **`failure(error)`**: Helper to create Failure tuple
### Tagged Errors
The `taggedError(tag)` function creates error classes with a `_tag` property for discriminated union pattern matching:
```typescript
const DatabaseError = taggedError('DatabaseError')
const err = new DatabaseError('connection failed')
// err._tag === 'DatabaseError'
// err instanceof Error === true
```
## Dual Package Support
The package supports both CommonJS and ESM consumers:
```json
{
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"exports": {
"require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" },
"import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }
}
}
```
## CI/CD
GitHub Actions workflow (`.github/workflows/main.yml`):
### Test Job
- Runs on every push and PR to `main`/`master`
- Tests against Node.js versions: 18, 20, 22
- Steps: checkout → setup Node → install → build → test with coverage
- `fail-fast: false` to see results for all Node versions
- Coverage uploaded to Codecov (Node 22 only)
### Publish Job
- Runs only on tag pushes (refs/tags/v*)
- Depends on successful test job
- Publishes to npm with provenance
- Requires `NPM_TOKEN` secret
## Security Considerations
- Zero runtime dependencies - reduces supply chain attack surface
- Dev dependencies are locked via `package-lock.json`
- Uses `type: "module"` for native ESM support
- npm publishing uses provenance for supply chain security
- CI has minimal permissions (`contents: read`, `id-token: write`)
## Development Workflow
1. Make changes to `src/index.ts`
2. Add/update tests in `src/index.test.ts`
3. Run `npm test` to verify build, lint, and tests pass
4. The CI will test against multiple Node.js versions on push
5. To release: push a version tag (e.g., `v7.2.1`) to trigger npm publish
## Notes for AI Agents
- Always run `npm test` after making changes to ensure build, lint, and tests pass
- Type tests with `@ark/attest` are as important as runtime tests
- The library has zero dependencies - avoid adding any
- Maintain dual CJS/ESM compatibility when making changes
- Follow the existing function overload patterns for type inference
- Error handling should preserve the Go-style tuple return pattern
- Use `taggedError()` for creating discriminated error types
- The `goTryAll` function supports both promise arrays and factory function arrays for lazy execution
- When adding new functions, include both runtime tests and type tests with `attest()`