@hulla/control 0.0.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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/package.json +126 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 @hulla
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # @hulla/control
2
+
3
+ A TypeScript library for functional error handling using the Result pattern. This library provides a robust and type-safe way to handle errors without throwing exceptions, making error handling more predictable and easier to reason about.
4
+
5
+ ## Features
6
+
7
+ - ๐ŸŽฏ Type-safe error handling with `Result<T, E>` type
8
+ - ๐Ÿ”„ Functional approach with pattern matching
9
+ - โšก Support for both synchronous and asynchronous operations
10
+ - ๐Ÿท๏ธ Custom tagging for better error categorization
11
+ - ๐Ÿ” Comprehensive TypeScript type definitions
12
+ - ๐Ÿงช Well-tested and production-ready
13
+
14
+ ## Installation
15
+
16
+ ### Node.js
17
+
18
+ ```bash
19
+ # Using npm
20
+ npm install @hulla/control
21
+
22
+ # Using yarn
23
+ yarn add @hulla/control
24
+
25
+ # Using pnpm
26
+ pnpm add @hulla/control
27
+
28
+ # Using bun
29
+ bun add @hulla/control
30
+ ```
31
+
32
+ ### Deno
33
+
34
+ ```typescript
35
+ import { ok, err, type Result } from "npm:@hulla/control"
36
+ ```
37
+
38
+ Or add it to your `deno.json`:
39
+
40
+ ```json
41
+ {
42
+ "imports": {
43
+ "@hulla/control": "npm:@hulla/control"
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Basic Usage
51
+
52
+ ```typescript
53
+ import { ok, err, type Result } from '@hulla/control'
54
+
55
+ interface User {
56
+ id: string
57
+ name: string
58
+ age: number
59
+ }
60
+
61
+ function checkAge(user: User) {
62
+ if (user.age < 18) {
63
+ return err(new Error("You must be at least 18 to enter"))
64
+ }
65
+ return ok(user)
66
+ }
67
+
68
+ const checkedUser = checkAge(20) // Ok<User> | Err<Error>
69
+
70
+ // Type safe handling
71
+ if (checkedUser.isOk()) {
72
+ const user = checkedUser.value
73
+ console.log(`User ${user.name}, ${user.email}`)
74
+ } else {
75
+ // Can also use isErr instead of isOk
76
+ const message = checkedUser.error
77
+ console.error(error)
78
+ }
79
+
80
+ // Use result pattern matching (rust-like) ๐Ÿฆ€
81
+ checkedUser.match(
82
+ user => console.log(`User ${user.name}, ${user.email}`)
83
+ error => console.error(error.message)
84
+ )
85
+
86
+ // Or pair handling (go-like) ๐Ÿฆซ๐ŸŒ€
87
+ const [user, error] = checkedUser.pair()
88
+ if (error) // ...
89
+ if (user) // ...
90
+ ```
91
+
92
+ ### Handling error-prone functions/values
93
+
94
+ > Not sure about an external library potentially throwing an error you have no control over?
95
+
96
+ ### Using `tcf` - typed version of Try-Catch-Finally
97
+
98
+ ```typescript
99
+ import { tcf } from '@hulla/control'
100
+ import { doSomething } form 'some-external-library'
101
+
102
+ async function safeDoSomething(path: string) {
103
+ return tcf({
104
+ try: () => {
105
+ return doSomething(path) // -> string
106
+ },
107
+ // can also be a other (custom) error type instead of just Error
108
+ catch: (error: Error) => new Error( // must return an error
109
+ `Failed to read ${path}: ${error.message}`
110
+ ),
111
+ finally: () => console.log('File operation completed') // optional
112
+ })
113
+ }
114
+
115
+ const result = safeDoSomething('package.json') // Ok<string> | Err<Error>
116
+
117
+ // Rest of error/value handling like in the example above ^
118
+ ```
119
+
120
+ ### Using `result`
121
+
122
+ Alternatively, if you know the code and know it will only return error/value type, i.e. `string | Error` you can use `result` to conver it to `Ok<string> | Err<Error>` type.
123
+
124
+ ```typescript
125
+ import { result } from '@hulla/control'
126
+
127
+ const valueOrError = doSomething() // string | Error
128
+ const converted = result(valueOrError) // Ok<string> | Err<Error>
129
+ ```
130
+
131
+
132
+ ### Async Operations
133
+
134
+ ```typescript
135
+ const asyncResult = ok(Promise.resolve(42))
136
+
137
+ // Pattern matching preserves Promise
138
+ const doubled = await asyncResult.match(
139
+ async value => value * 2,
140
+ error => 0
141
+ )
142
+
143
+ // Pair method also handles Promises
144
+ const [value, error] = await asyncResult.pair()
145
+ ```
146
+
147
+ ### Result Tagging
148
+
149
+ Tagging allows you to add semantic meaning to your results, making it easier to categorize and handle different types of successes and errors. This is particularly useful when you need to:
150
+
151
+ 1. Distinguish between different types of errors (e.g., validation, network, auth)
152
+ 2. Categorize successful operations (e.g., created, updated, cached)
153
+ 3. Build type-safe error handling flows
154
+
155
+ ```typescript
156
+ import { ok, err, tcf, type Result } from '@hulla/control'
157
+
158
+ // Basic tagging
159
+ const success = ok("Data loaded", "FETCH_SUCCESS")
160
+ const failure = err(new Error("Network error"), "NETWORK_ERROR")
161
+
162
+ // Tagged results in functions
163
+ function validateUser(data: unknown): Result<User, Error> {
164
+ return tcf({
165
+ try: () => {
166
+ if (!data.name) throw new Error("Name required")
167
+ return data as User
168
+ },
169
+ catch: (error: Error) => error,
170
+ tagOk: "VALIDATION_SUCCESS",
171
+ tagError: "VALIDATION_ERROR"
172
+ })
173
+ }
174
+
175
+ // Type-safe handling based on tags
176
+ const result = validateUser(data)
177
+ if (result.isErr() && result.tag === "VALIDATION_ERROR") {
178
+ // Handle validation errors specifically
179
+ showValidationError(result.error)
180
+ }
181
+
182
+ // Tagging with tcf wrapper
183
+ function fetchUserData(id: string) {
184
+ return tcf({
185
+ try: () => api.getUser(id),
186
+ catch: (error: Error) => error,
187
+ tagOk: "USER_FOUND",
188
+ tagError: "USER_NOT_FOUND",
189
+ finally: () => console.log("Fetch completed")
190
+ })
191
+ }
192
+
193
+ // Pattern matching with tagged results
194
+ const userResult = await fetchUserData("123")
195
+ userResult.match(
196
+ user => {
197
+ if (userResult.tag === "USER_FOUND") {
198
+ updateUI(user)
199
+ }
200
+ // ...
201
+ },
202
+ error => {
203
+ if (userResult.tag === "USER_NOT_FOUND") {
204
+ showNotFoundError()
205
+ }
206
+ // ....
207
+ }
208
+ )
209
+ ```
210
+
211
+ ## API Reference
212
+
213
+ ### Types
214
+
215
+ - `Result<T, E>` - Main result type that can be either `Ok<T>` or `Err<E>`
216
+ - `Ok<T>` - Success type containing a value of type `T`
217
+ - `Err<E>` - Error type containing an error of type `E`
218
+ - `Tagged<T, Tag>` - Result type with custom string tag
219
+
220
+ ### Functions
221
+
222
+ - `ok<T>(value: T): Ok<T>` - Create a success result
223
+ - `err<E>(error: E): Err<E>` - Create an error result
224
+ - `tcf(options)` - Functional try-catch-finally wrapper
225
+ - `result(value, config?)` - Create a Result from a value that might be an error
226
+
227
+ ### Methods
228
+
229
+ Each Result instance provides:
230
+
231
+ - `isOk()` - Type guard for success case
232
+ - `isErr()` - Type guard for error case
233
+ - `match(okFn, errFn)` - Pattern matching
234
+ - `pair()` - Convert to [value, error] tuple
235
+ - `unwrap()` - Get raw value/error
236
+
237
+ ## License
238
+
239
+ MIT License - see the [LICENSE](LICENSE) file for details.
240
+
241
+ ## Author
242
+
243
+ Samuel Hulla ([@samuelhulla](https://github.com/hulladev))
244
+
245
+ ## Contributing
246
+
247
+ Contributions are welcome! Please feel free to submit a Pull Request.
package/package.json ADDED
@@ -0,0 +1,126 @@
1
+ {
2
+ "name": "@hulla/control",
3
+ "version": "0.0.0",
4
+ "author": {
5
+ "name": "Samuel Hulla",
6
+ "email": "hulla@hulla.dev",
7
+ "url": "https://hulla.dev"
8
+ },
9
+ "maintainers": [
10
+ "Samuel Hulla <hulla@hulla.dev>"
11
+ ],
12
+ "homepage": "https://hulla.dev/projects/args",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/hulladev/args.git",
16
+ "directory": "packages/args"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "main": "./dist/cjs/index.js",
22
+ "module": "./dist/es/index.mjs",
23
+ "types": "./dist/cjs/index.d.ts",
24
+ "exports": {
25
+ "import": {
26
+ "types": "./dist/es/index.d.mts",
27
+ "default": "./dist/es/index.mjs"
28
+ },
29
+ "require": {
30
+ "types": "./dist/cjs/index.d.ts",
31
+ "default": "./dist/cjs/index.js"
32
+ }
33
+ },
34
+ "devDependencies": {
35
+ "@changesets/cli": "^2.29.5",
36
+ "@eslint/js": "^9.29.0",
37
+ "@types/node": "^24.0.7",
38
+ "@vitest/ui": "^3.2.4",
39
+ "bunchee": "^6.5.4",
40
+ "commitizen": "^4.3.1",
41
+ "cz-emoji": "1.3.2-canary.2",
42
+ "eslint": "^9.29.0",
43
+ "eslint-config-prettier": "^10.1.5",
44
+ "eslint-plugin-prettier": "^5.4.1",
45
+ "husky": "^9.1.7",
46
+ "prettier": "^3.5.3",
47
+ "prettier-plugin-organize-imports": "^4.1.0",
48
+ "typescript": "^5.8.3",
49
+ "typescript-eslint": "^8.34.0",
50
+ "vitest": "^3.2.4"
51
+ },
52
+ "config": {
53
+ "commitizen": {
54
+ "path": "./node_modules/cz-emoji"
55
+ },
56
+ "cz-emoji": {
57
+ "skipScope": true,
58
+ "types": [
59
+ {
60
+ "emoji": "โœ…",
61
+ "code": ":white_check_mark: feat:",
62
+ "description": "a new functionality",
63
+ "name": "feat"
64
+ },
65
+ {
66
+ "emoji": "๐Ÿž",
67
+ "code": ":lady_beetle: fix:",
68
+ "description": "a bug fix",
69
+ "name": "fix"
70
+ },
71
+ {
72
+ "emoji": "๐Ÿ”ง",
73
+ "code": ":wrench: update:",
74
+ "description": "a code change that neither fixes a bug nor adds a feature",
75
+ "name": "update"
76
+ },
77
+ {
78
+ "emoji": "๐Ÿ“š",
79
+ "code": ":books: docs:",
80
+ "description": "documentations",
81
+ "name": "docs"
82
+ },
83
+ {
84
+ "emoji": "๐Ÿงช",
85
+ "code": ":test_tube: tests:",
86
+ "description": "tests",
87
+ "name": "tests"
88
+ },
89
+ {
90
+ "emoji": "๐Ÿช›",
91
+ "code": ":screwdriver: config:",
92
+ "description": "configuration files",
93
+ "name": "config"
94
+ },
95
+ {
96
+ "emoji": "๐Ÿค–",
97
+ "code": ":robot: devops:",
98
+ "description": "ci/cd or other form of automation",
99
+ "name": "devops"
100
+ },
101
+ {
102
+ "emoji": "โ™ป๏ธ",
103
+ "code": ":recycle: cleanup:",
104
+ "description": "code cleanup",
105
+ "name": "cleanup"
106
+ },
107
+ {
108
+ "emoji": "๐Ÿ“ฆ",
109
+ "code": ":package: release:",
110
+ "description": "new release bundle",
111
+ "name": "release"
112
+ }
113
+ ]
114
+ }
115
+ },
116
+ "scripts": {
117
+ "build": "bunchee -m",
118
+ "test": "vitest",
119
+ "test:ui": "vitest --ui",
120
+ "test:run": "vitest run",
121
+ "lint": "eslint . --ext .js,.ts,.mjs,.mts",
122
+ "lint:fix": "eslint . --ext .js,.ts,.mjs,.mts --fix",
123
+ "format": "prettier --write \"**/*.{js,ts,mjs,mts,json,md}\"",
124
+ "format:check": "prettier --check \"**/*.{js,ts,mjs,mts,json,md}\""
125
+ }
126
+ }