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 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**: 6.2.0
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.8.3
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 goTry, goTryRaw, and utility types
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 enabled
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
- - **Configuration**: Uses Biome's default configuration (no biome.json present)
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
- ### Global Setup
130
+ ### Coverage Configuration
113
131
 
114
- The `setupVitest.ts` file configures `@ark/attest` for type assertions in tests.
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
- - **`goTry<T>(value)`**: Returns `[string | undefined, T | undefined]` - error is the message string
121
- - **`goTryRaw<T, E>(value)`**: Returns `[E | undefined, T | undefined]` - error is the raw Error object
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 | undefined, T | undefined]`
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
- ### Input Handling
169
+ ### Tagged Errors
132
170
 
133
- Both functions accept:
134
- - Direct values
135
- - Functions (sync or async)
136
- - Promises
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`) runs on every push and PR:
199
+ GitHub Actions workflow (`.github/workflows/main.yml`):
157
200
 
158
- - Tests against Node.js versions: 12, 14, 16, 18
159
- - Uses `yarn install` and `yarn test`
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
- function getErrorMessage(error) {
103
- if (error === void 0) return "undefined";
104
- if (typeof error === "string") return error;
105
- if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
106
- return error.message;
107
- }
108
- try {
109
- return JSON.stringify(error);
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
- return success(result);
127
- } catch (err) {
128
- return failure(getErrorMessage(err));
129
- }
164
+ };
130
165
  }
131
- function goTryRaw(value, ErrorClass) {
132
- const wrapError = (err) => {
133
- if (ErrorClass) {
134
- if (err === void 0) {
135
- return new ErrorClass("undefined");
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
- return isError(err) ? err : new Error(String(err));
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
- return success(result);
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;