go-go-try 7.2.0 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +81 -23
- package/README.md +0 -16
- package/dist/index.cjs +82 -59
- package/dist/index.d.cts +176 -115
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +176 -115
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +81 -60
- package/package.json +1 -1
- package/src/assert.ts +59 -0
- package/src/goTry.ts +45 -0
- package/src/goTryAll.ts +141 -0
- package/src/goTryOr.ts +55 -0
- package/src/goTryRaw.ts +89 -0
- package/src/index.test.ts +188 -21
- package/src/index.ts +23 -474
- package/src/internals.ts +45 -0
- package/src/result-helpers.ts +46 -0
- package/src/tagged-error.ts +38 -0
- package/src/types.ts +50 -0
package/AGENTS.md
CHANGED
|
@@ -5,25 +5,35 @@
|
|
|
5
5
|
**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.
|
|
6
6
|
|
|
7
7
|
- **Name**: go-go-try
|
|
8
|
-
- **Version**:
|
|
8
|
+
- **Version**: 7.2.1
|
|
9
9
|
- **License**: MIT
|
|
10
10
|
- **Repository**: thelinuxlich/go-go-try
|
|
11
11
|
- **Node.js Requirements**: >= 16
|
|
12
12
|
|
|
13
|
+
### Key Features
|
|
14
|
+
|
|
15
|
+
- Zero runtime dependencies
|
|
16
|
+
- Dual package support (CommonJS and ESM)
|
|
17
|
+
- Full TypeScript support with precise type inference
|
|
18
|
+
- Support for sync/async functions, promises, and direct values
|
|
19
|
+
- Parallel execution utilities with optional concurrency control
|
|
20
|
+
- Tagged errors for discriminated union pattern matching
|
|
21
|
+
|
|
13
22
|
## Technology Stack
|
|
14
23
|
|
|
15
|
-
- **Language**: TypeScript 5.
|
|
24
|
+
- **Language**: TypeScript 5.9.3
|
|
16
25
|
- **Build Tool**: [pkgroll](https://github.com/privatenumber/pkgroll) - A zero-config TypeScript package bundler
|
|
17
26
|
- **Linter**: [Biome](https://biomejs.dev/) - Fast linter and formatter
|
|
18
27
|
- **Test Framework**: [Vitest](https://vitest.dev/) - Vite-native unit test framework
|
|
19
28
|
- **Type Testing**: [@ark/attest](https://github.com/arktypeio/arktype) - Runtime type assertions for TypeScript
|
|
29
|
+
- **Git Hooks**: [Husky](https://typicode.github.io/husky/) + [lint-staged](https://github.com/lint-staged/lint-staged)
|
|
20
30
|
|
|
21
31
|
## Project Structure
|
|
22
32
|
|
|
23
33
|
```
|
|
24
34
|
.
|
|
25
35
|
├── src/
|
|
26
|
-
│ ├── index.ts # Main source file - exports
|
|
36
|
+
│ ├── index.ts # Main source file - exports all functions and types
|
|
27
37
|
│ └── index.test.ts # Comprehensive test suite with runtime and type tests
|
|
28
38
|
├── dist/ # Build output (generated by pkgroll)
|
|
29
39
|
│ ├── index.cjs # CommonJS build
|
|
@@ -32,10 +42,12 @@
|
|
|
32
42
|
│ └── index.d.mts # ES Module type definitions
|
|
33
43
|
├── .github/workflows/
|
|
34
44
|
│ └── main.yml # CI configuration for GitHub Actions
|
|
45
|
+
├── .husky/
|
|
46
|
+
│ └── pre-commit # Git pre-commit hook (runs lint-staged)
|
|
35
47
|
├── .attest/ # Ark attest cache directory
|
|
36
48
|
├── package.json # Package configuration with dual CJS/ESM exports
|
|
37
49
|
├── tsconfig.json # TypeScript strict configuration
|
|
38
|
-
├── vitest.config.ts # Vitest configuration with type checking
|
|
50
|
+
├── vitest.config.ts # Vitest configuration with type checking and coverage
|
|
39
51
|
├── setupVitest.ts # Vitest global setup for @ark/attest
|
|
40
52
|
└── README.md # User-facing documentation
|
|
41
53
|
```
|
|
@@ -51,17 +63,20 @@ npm run lint
|
|
|
51
63
|
|
|
52
64
|
# Run the full test suite (build + lint + vitest)
|
|
53
65
|
npm test
|
|
66
|
+
|
|
67
|
+
# Run tests with coverage report
|
|
68
|
+
npm run test:coverage
|
|
54
69
|
```
|
|
55
70
|
|
|
56
|
-
The test script is a composite that:
|
|
71
|
+
The `npm test` script is a composite that:
|
|
57
72
|
1. Builds the project
|
|
58
73
|
2. Runs the linter
|
|
59
|
-
3. Executes Vitest tests
|
|
74
|
+
3. Executes Vitest tests with type checking
|
|
60
75
|
|
|
61
76
|
## Code Style Guidelines
|
|
62
77
|
|
|
63
|
-
- **Linter**: Biome is used for linting and formatting
|
|
64
|
-
- **
|
|
78
|
+
- **Linter**: Biome is used for linting and formatting with default configuration (no biome.json present)
|
|
79
|
+
- **Pre-commit**: Husky runs `lint-staged` which lints all staged `.ts` files via Biome
|
|
65
80
|
- **Strict TypeScript**: The `tsconfig.json` enforces strict mode with additional checks:
|
|
66
81
|
- `noUnusedLocals`: true
|
|
67
82
|
- `noUnusedParameters`: true
|
|
@@ -78,8 +93,8 @@ Tests are co-located with source code in `src/index.test.ts` using Vitest.
|
|
|
78
93
|
|
|
79
94
|
### Test Types
|
|
80
95
|
|
|
81
|
-
1. **Runtime Tests**: Standard unit tests verifying behavior
|
|
82
|
-
2. **Type Tests**: Using `@ark/attest` to verify TypeScript type inference at runtime
|
|
96
|
+
1. **Runtime Tests**: Standard unit tests verifying behavior using Vitest's `assert` and `test`
|
|
97
|
+
2. **Type Tests**: Using `@ark/attest` to verify TypeScript type inference at runtime with `attest<T>(value)`
|
|
83
98
|
|
|
84
99
|
### Key Testing Patterns
|
|
85
100
|
|
|
@@ -107,33 +122,60 @@ npx vitest run
|
|
|
107
122
|
|
|
108
123
|
# Run tests in watch mode (during development)
|
|
109
124
|
npx vitest
|
|
125
|
+
|
|
126
|
+
# Run with coverage
|
|
127
|
+
npx vitest run --coverage
|
|
110
128
|
```
|
|
111
129
|
|
|
112
|
-
###
|
|
130
|
+
### Coverage Configuration
|
|
113
131
|
|
|
114
|
-
|
|
132
|
+
Coverage is provided by `@vitest/coverage-v8` with reporters: text, json, html, and lcov. Excluded paths:
|
|
133
|
+
- `node_modules/`
|
|
134
|
+
- `dist/`
|
|
135
|
+
- `**/*.test.ts`
|
|
136
|
+
- `setupVitest.ts`
|
|
115
137
|
|
|
116
138
|
## API Design
|
|
117
139
|
|
|
118
140
|
### Core Functions
|
|
119
141
|
|
|
120
|
-
|
|
121
|
-
|
|
142
|
+
| Function | Description | Error Type |
|
|
143
|
+
|----------|-------------|------------|
|
|
144
|
+
| `goTry<T>(value)` | Returns `[string \| undefined, T \| undefined]` | Error message string |
|
|
145
|
+
| `goTryRaw<T, E>(value, ErrorClass?)` | Returns `[E \| undefined, T \| undefined]` | Raw Error object or tagged error |
|
|
146
|
+
| `goTryOr<T>(value, defaultValue)` | Returns `[string \| undefined, T]` | Error message with fallback default |
|
|
147
|
+
| `goTryAll<T>(items, options?)` | Parallel execution, returns `[errors[], results[]]` | Error message strings |
|
|
148
|
+
| `goTryAllRaw<T>(items, options?)` | Parallel execution, returns `[Error[], results[]]` | Raw Error objects |
|
|
149
|
+
|
|
150
|
+
### Input Handling
|
|
151
|
+
|
|
152
|
+
All `goTry*` functions accept:
|
|
153
|
+
- Direct values
|
|
154
|
+
- Functions (sync or async)
|
|
155
|
+
- Promises
|
|
122
156
|
|
|
123
157
|
### Type Helpers
|
|
124
158
|
|
|
125
|
-
- **`Result<E, T>`**: The tuple type `[E
|
|
159
|
+
- **`Result<E, T>`**: The tuple type `[E \| undefined, T \| undefined]`
|
|
126
160
|
- **`Success<T>`**: `[undefined, T]`
|
|
127
161
|
- **`Failure<E>`**: `[E, undefined]`
|
|
162
|
+
- **`TaggedError<T>`**: Interface for discriminated errors with `_tag` property
|
|
163
|
+
- **`TaggedUnion<T>`**: Creates union type from multiple tagged error classes
|
|
128
164
|
- **`isSuccess(result)`**: Type guard to check if result is success
|
|
129
165
|
- **`isFailure(result)`**: Type guard to check if result is failure
|
|
166
|
+
- **`success(value)`**: Helper to create Success tuple
|
|
167
|
+
- **`failure(error)`**: Helper to create Failure tuple
|
|
130
168
|
|
|
131
|
-
###
|
|
169
|
+
### Tagged Errors
|
|
132
170
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
The `taggedError(tag)` function creates error classes with a `_tag` property for discriminated union pattern matching:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const DatabaseError = taggedError('DatabaseError')
|
|
175
|
+
const err = new DatabaseError('connection failed')
|
|
176
|
+
// err._tag === 'DatabaseError'
|
|
177
|
+
// err instanceof Error === true
|
|
178
|
+
```
|
|
137
179
|
|
|
138
180
|
## Dual Package Support
|
|
139
181
|
|
|
@@ -141,6 +183,7 @@ The package supports both CommonJS and ESM consumers:
|
|
|
141
183
|
|
|
142
184
|
```json
|
|
143
185
|
{
|
|
186
|
+
"type": "module",
|
|
144
187
|
"main": "./dist/index.cjs",
|
|
145
188
|
"module": "./dist/index.mjs",
|
|
146
189
|
"types": "./dist/index.d.mts",
|
|
@@ -153,17 +196,28 @@ The package supports both CommonJS and ESM consumers:
|
|
|
153
196
|
|
|
154
197
|
## CI/CD
|
|
155
198
|
|
|
156
|
-
GitHub Actions workflow (`.github/workflows/main.yml`)
|
|
199
|
+
GitHub Actions workflow (`.github/workflows/main.yml`):
|
|
157
200
|
|
|
158
|
-
|
|
159
|
-
-
|
|
201
|
+
### Test Job
|
|
202
|
+
- Runs on every push and PR to `main`/`master`
|
|
203
|
+
- Tests against Node.js versions: 18, 20, 22
|
|
204
|
+
- Steps: checkout → setup Node → install → build → test with coverage
|
|
160
205
|
- `fail-fast: false` to see results for all Node versions
|
|
206
|
+
- Coverage uploaded to Codecov (Node 22 only)
|
|
207
|
+
|
|
208
|
+
### Publish Job
|
|
209
|
+
- Runs only on tag pushes (refs/tags/v*)
|
|
210
|
+
- Depends on successful test job
|
|
211
|
+
- Publishes to npm with provenance
|
|
212
|
+
- Requires `NPM_TOKEN` secret
|
|
161
213
|
|
|
162
214
|
## Security Considerations
|
|
163
215
|
|
|
164
216
|
- Zero runtime dependencies - reduces supply chain attack surface
|
|
165
217
|
- Dev dependencies are locked via `package-lock.json`
|
|
166
218
|
- Uses `type: "module"` for native ESM support
|
|
219
|
+
- npm publishing uses provenance for supply chain security
|
|
220
|
+
- CI has minimal permissions (`contents: read`, `id-token: write`)
|
|
167
221
|
|
|
168
222
|
## Development Workflow
|
|
169
223
|
|
|
@@ -171,6 +225,7 @@ GitHub Actions workflow (`.github/workflows/main.yml`) runs on every push and PR
|
|
|
171
225
|
2. Add/update tests in `src/index.test.ts`
|
|
172
226
|
3. Run `npm test` to verify build, lint, and tests pass
|
|
173
227
|
4. The CI will test against multiple Node.js versions on push
|
|
228
|
+
5. To release: push a version tag (e.g., `v7.2.1`) to trigger npm publish
|
|
174
229
|
|
|
175
230
|
## Notes for AI Agents
|
|
176
231
|
|
|
@@ -180,3 +235,6 @@ GitHub Actions workflow (`.github/workflows/main.yml`) runs on every push and PR
|
|
|
180
235
|
- Maintain dual CJS/ESM compatibility when making changes
|
|
181
236
|
- Follow the existing function overload patterns for type inference
|
|
182
237
|
- Error handling should preserve the Go-style tuple return pattern
|
|
238
|
+
- Use `taggedError()` for creating discriminated error types
|
|
239
|
+
- The `goTryAll` function supports both promise arrays and factory function arrays for lazy execution
|
|
240
|
+
- When adding new functions, include both runtime tests and type tests with `attest()`
|
package/README.md
CHANGED
|
@@ -453,22 +453,6 @@ console.log(err.name) // 'DatabaseError'
|
|
|
453
453
|
console.log(err.cause) // originalError
|
|
454
454
|
```
|
|
455
455
|
|
|
456
|
-
### `TaggedInstance<T>`
|
|
457
|
-
|
|
458
|
-
Extracts the instance type from a tagged error class. Cleaner alternative to `InstanceType<typeof ErrorClass>`.
|
|
459
|
-
|
|
460
|
-
```ts
|
|
461
|
-
type TaggedInstance<T extends ErrorConstructor<unknown>> =
|
|
462
|
-
T extends ErrorConstructor<infer E> ? E : never
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
**Example:**
|
|
466
|
-
```ts
|
|
467
|
-
const DatabaseError = taggedError('DatabaseError')
|
|
468
|
-
type DbError = TaggedInstance<typeof DatabaseError>
|
|
469
|
-
// Equivalent to: InstanceType<typeof DatabaseError>
|
|
470
|
-
```
|
|
471
|
-
|
|
472
456
|
### `TaggedUnion<T>`
|
|
473
457
|
|
|
474
458
|
Creates a union type from multiple tagged error classes.
|
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
function taggedError(tag) {
|
|
4
|
-
return class TaggedErrorClass extends Error {
|
|
5
|
-
constructor(message, options) {
|
|
6
|
-
super(message);
|
|
7
|
-
this._tag = tag;
|
|
8
|
-
this.name = tag;
|
|
9
|
-
this.cause = options?.cause;
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
3
|
function isSuccess(result) {
|
|
14
4
|
return result[0] === void 0;
|
|
15
5
|
}
|
|
@@ -22,9 +12,71 @@ function success(value) {
|
|
|
22
12
|
function failure(error) {
|
|
23
13
|
return [error, void 0];
|
|
24
14
|
}
|
|
15
|
+
function assertNever(value) {
|
|
16
|
+
throw new Error(`Unhandled case: ${String(value)}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getErrorMessage(error) {
|
|
20
|
+
if (error === void 0) return "undefined";
|
|
21
|
+
if (typeof error === "string") return error;
|
|
22
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
23
|
+
return error.message;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return JSON.stringify(error);
|
|
27
|
+
} catch {
|
|
28
|
+
return String(error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isPromise(value) {
|
|
32
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
33
|
+
}
|
|
34
|
+
function isError(value) {
|
|
35
|
+
return value instanceof Error;
|
|
36
|
+
}
|
|
25
37
|
function resolveDefault(defaultValue) {
|
|
26
38
|
return typeof defaultValue === "function" ? defaultValue() : defaultValue;
|
|
27
39
|
}
|
|
40
|
+
|
|
41
|
+
function goTry(value) {
|
|
42
|
+
try {
|
|
43
|
+
const result = typeof value === "function" ? value() : value;
|
|
44
|
+
if (isPromise(result)) {
|
|
45
|
+
return result.then((resolvedValue) => success(resolvedValue)).catch((err) => failure(getErrorMessage(err)));
|
|
46
|
+
}
|
|
47
|
+
return success(result);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
return failure(getErrorMessage(err));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function goTryRaw(value, ErrorClass) {
|
|
54
|
+
const wrapError = (err) => {
|
|
55
|
+
if (ErrorClass) {
|
|
56
|
+
if (err === void 0) {
|
|
57
|
+
return new ErrorClass("undefined");
|
|
58
|
+
}
|
|
59
|
+
if (isError(err)) {
|
|
60
|
+
return new ErrorClass(err.message, { cause: err });
|
|
61
|
+
}
|
|
62
|
+
return new ErrorClass(String(err));
|
|
63
|
+
}
|
|
64
|
+
if (err === void 0) {
|
|
65
|
+
return new Error("undefined");
|
|
66
|
+
}
|
|
67
|
+
return isError(err) ? err : new Error(String(err));
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
const result = typeof value === "function" ? value() : value;
|
|
71
|
+
if (isPromise(result)) {
|
|
72
|
+
return result.then((resolvedValue) => success(resolvedValue)).catch((err) => failure(wrapError(err)));
|
|
73
|
+
}
|
|
74
|
+
return success(result);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return failure(wrapError(err));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
28
80
|
function goTryOr(value, defaultValue) {
|
|
29
81
|
try {
|
|
30
82
|
const result = typeof value === "function" ? value() : value;
|
|
@@ -36,6 +88,7 @@ function goTryOr(value, defaultValue) {
|
|
|
36
88
|
return [getErrorMessage(err), resolveDefault(defaultValue)];
|
|
37
89
|
}
|
|
38
90
|
}
|
|
91
|
+
|
|
39
92
|
async function runWithConcurrency(items, concurrency) {
|
|
40
93
|
if (items.length === 0) {
|
|
41
94
|
return [];
|
|
@@ -99,62 +152,32 @@ async function goTryAllRaw(items, options) {
|
|
|
99
152
|
}
|
|
100
153
|
return [errors, results];
|
|
101
154
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
} catch {
|
|
111
|
-
return String(error);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function isPromise(value) {
|
|
115
|
-
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
116
|
-
}
|
|
117
|
-
function isError(value) {
|
|
118
|
-
return value instanceof Error;
|
|
119
|
-
}
|
|
120
|
-
function goTry(value) {
|
|
121
|
-
try {
|
|
122
|
-
const result = typeof value === "function" ? value() : value;
|
|
123
|
-
if (isPromise(result)) {
|
|
124
|
-
return result.then((resolvedValue) => success(resolvedValue)).catch((err) => failure(getErrorMessage(err)));
|
|
155
|
+
|
|
156
|
+
function taggedError(tag) {
|
|
157
|
+
return class TaggedErrorClass extends Error {
|
|
158
|
+
constructor(message, options) {
|
|
159
|
+
super(message);
|
|
160
|
+
this._tag = tag;
|
|
161
|
+
this.name = tag;
|
|
162
|
+
this.cause = options?.cause;
|
|
125
163
|
}
|
|
126
|
-
|
|
127
|
-
} catch (err) {
|
|
128
|
-
return failure(getErrorMessage(err));
|
|
129
|
-
}
|
|
164
|
+
};
|
|
130
165
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
if (isError(err)) {
|
|
138
|
-
return new ErrorClass(err.message, { cause: err });
|
|
139
|
-
}
|
|
140
|
-
return new ErrorClass(String(err));
|
|
141
|
-
}
|
|
142
|
-
if (err === void 0) {
|
|
143
|
-
return new Error("undefined");
|
|
166
|
+
|
|
167
|
+
function assert(condition, errorOrClass, message) {
|
|
168
|
+
if (!condition) {
|
|
169
|
+
if (typeof errorOrClass === "string") {
|
|
170
|
+
throw new Error(errorOrClass);
|
|
144
171
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
const result = typeof value === "function" ? value() : value;
|
|
149
|
-
if (isPromise(result)) {
|
|
150
|
-
return result.then((resolvedValue) => success(resolvedValue)).catch((err) => failure(wrapError(err)));
|
|
172
|
+
if (typeof errorOrClass === "function" && message !== void 0) {
|
|
173
|
+
throw new errorOrClass(message);
|
|
151
174
|
}
|
|
152
|
-
|
|
153
|
-
} catch (err) {
|
|
154
|
-
return failure(wrapError(err));
|
|
175
|
+
throw errorOrClass;
|
|
155
176
|
}
|
|
156
177
|
}
|
|
157
178
|
|
|
179
|
+
exports.assert = assert;
|
|
180
|
+
exports.assertNever = assertNever;
|
|
158
181
|
exports.failure = failure;
|
|
159
182
|
exports.goTry = goTry;
|
|
160
183
|
exports.goTryAll = goTryAll;
|