happy-rusty 1.5.0 → 1.6.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/CHANGELOG.md +206 -0
- package/README.cn.md +265 -19
- package/README.md +261 -21
- package/dist/main.cjs +382 -32
- package/dist/main.cjs.map +1 -1
- package/dist/main.mjs +374 -33
- package/dist/main.mjs.map +1 -1
- package/dist/types.d.ts +2002 -52
- package/package.json +37 -24
- package/dist/types.d.ts.map +0 -1
- package/docs/README.md +0 -47
- package/docs/functions/Err.md +0 -46
- package/docs/functions/Ok.md +0 -70
- package/docs/functions/Some.md +0 -45
- package/docs/functions/isOption.md +0 -35
- package/docs/functions/isResult.md +0 -36
- package/docs/functions/promiseToAsyncResult.md +0 -50
- package/docs/interfaces/None.md +0 -979
- package/docs/interfaces/Option.md +0 -857
- package/docs/interfaces/Result.md +0 -903
- package/docs/type-aliases/AsyncIOResult.md +0 -24
- package/docs/type-aliases/AsyncOption.md +0 -24
- package/docs/type-aliases/AsyncResult.md +0 -25
- package/docs/type-aliases/AsyncVoidIOResult.md +0 -17
- package/docs/type-aliases/AsyncVoidResult.md +0 -23
- package/docs/type-aliases/IOResult.md +0 -24
- package/docs/type-aliases/VoidIOResult.md +0 -17
- package/docs/type-aliases/VoidResult.md +0 -23
- package/docs/variables/None.md +0 -18
- package/docs/variables/RESULT_FALSE.md +0 -18
- package/docs/variables/RESULT_TRUE.md +0 -18
- package/docs/variables/RESULT_VOID.md +0 -17
- package/docs/variables/RESULT_ZERO.md +0 -18
- package/src/enum/constants.ts +0 -30
- package/src/enum/core.ts +0 -635
- package/src/enum/defines.ts +0 -45
- package/src/enum/extensions.ts +0 -31
- package/src/enum/mod.ts +0 -6
- package/src/enum/prelude.ts +0 -619
- package/src/enum/symbols.ts +0 -9
- package/src/enum/utils.ts +0 -27
- package/src/mod.ts +0 -1
package/README.md
CHANGED
|
@@ -1,53 +1,293 @@
|
|
|
1
|
-
#
|
|
1
|
+
# happy-rusty
|
|
2
|
+
|
|
3
|
+
Rust's `Option`, `Result`, and sync primitives for JavaScript/TypeScript - Better error handling and null-safety patterns.
|
|
2
4
|
|
|
3
5
|
[](https://npmjs.org/package/happy-rusty)
|
|
4
6
|
[](https://npmjs.org/package/happy-rusty)
|
|
5
7
|
[](https://jsr.io/@happy-js/happy-rusty)
|
|
6
8
|
[](https://jsr.io/@happy-js/happy-rusty/score)
|
|
7
|
-
[](https://github.com/JiangJie/happy-rusty/actions/workflows/test.yml)
|
|
8
10
|
[](https://codecov.io/gh/JiangJie/happy-rusty)
|
|
9
11
|
|
|
10
12
|
---
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
[中文](README.cn.md)
|
|
13
15
|
|
|
14
16
|
---
|
|
15
17
|
|
|
16
|
-
##
|
|
18
|
+
## Features
|
|
17
19
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
+
- **Option<T>** - Represents an optional value: every `Option` is either `Some(T)` or `None`
|
|
21
|
+
- **Result<T, E>** - Represents either success (`Ok(T)`) or failure (`Err(E)`)
|
|
22
|
+
- **Sync Primitives** - Rust-inspired `Once<T>`, `Lazy<T>`, `LazyAsync<T>`, and `Mutex<T>`
|
|
23
|
+
- **Control Flow** - `ControlFlow<B, C>` with `Break` and `Continue` for short-circuiting operations
|
|
24
|
+
- **Full TypeScript support** with strict type inference
|
|
25
|
+
- **Async support** - Async versions of all transformation methods
|
|
26
|
+
- **Zero dependencies**
|
|
27
|
+
- **Runtime immutability** - All instances are frozen with `Object.freeze()`
|
|
28
|
+
- **Cross-runtime** - Works in Node.js, Deno, Bun, and browsers
|
|
20
29
|
|
|
21
30
|
## Installation
|
|
22
31
|
|
|
23
32
|
```sh
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
# npm
|
|
34
|
+
npm install happy-rusty
|
|
35
|
+
|
|
36
|
+
# yarn
|
|
27
37
|
yarn add happy-rusty
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
#
|
|
38
|
+
|
|
39
|
+
# pnpm
|
|
40
|
+
pnpm add happy-rusty
|
|
41
|
+
|
|
42
|
+
# JSR (Deno)
|
|
33
43
|
deno add @happy-js/happy-rusty
|
|
34
|
-
|
|
44
|
+
|
|
45
|
+
# JSR (Bun)
|
|
35
46
|
bunx jsr add @happy-js/happy-rusty
|
|
36
47
|
```
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
## Quick Start
|
|
39
50
|
|
|
40
51
|
```ts
|
|
41
52
|
import { Some, None, Ok, Err } from 'happy-rusty';
|
|
53
|
+
|
|
54
|
+
// Option - handling nullable values
|
|
55
|
+
function findUser(id: number): Option<User> {
|
|
56
|
+
const user = database.get(id);
|
|
57
|
+
return user ? Some(user) : None;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const user = findUser(1)
|
|
61
|
+
.map(u => u.name)
|
|
62
|
+
.unwrapOr('Guest');
|
|
63
|
+
|
|
64
|
+
// Result - handling errors
|
|
65
|
+
function parseJSON<T>(json: string): Result<T, Error> {
|
|
66
|
+
try {
|
|
67
|
+
return Ok(JSON.parse(json));
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return Err(e as Error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const config = parseJSON<Config>(jsonStr)
|
|
74
|
+
.map(c => c.settings)
|
|
75
|
+
.unwrapOrElse(err => {
|
|
76
|
+
console.error('Parse failed:', err);
|
|
77
|
+
return defaultSettings;
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API Overview
|
|
82
|
+
|
|
83
|
+
### Option<T>
|
|
84
|
+
|
|
85
|
+
| Category | Methods |
|
|
86
|
+
|----------|---------|
|
|
87
|
+
| **Constructors** | `Some(value)`, `None` |
|
|
88
|
+
| **Querying** | `isSome()`, `isNone()`, `isSomeAnd(fn)` |
|
|
89
|
+
| **Extracting** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
|
|
90
|
+
| **Transforming** | `map(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `filter(fn)`, `flatten()` |
|
|
91
|
+
| **Boolean ops** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)`, `xor(other)` |
|
|
92
|
+
| **Converting** | `okOr(err)`, `okOrElse(fn)`, `transpose()` |
|
|
93
|
+
| **Combining** | `zip(other)`, `zipWith(other, fn)`, `unzip()` |
|
|
94
|
+
| **Side effects** | `inspect(fn)` |
|
|
95
|
+
| **Comparison** | `eq(other)` |
|
|
96
|
+
|
|
97
|
+
### Result<T, E>
|
|
98
|
+
|
|
99
|
+
| Category | Methods |
|
|
100
|
+
|----------|---------|
|
|
101
|
+
| **Constructors** | `Ok(value)`, `Ok()` (void), `Err(error)` |
|
|
102
|
+
| **Querying** | `isOk()`, `isErr()`, `isOkAnd(fn)`, `isErrAnd(fn)` |
|
|
103
|
+
| **Extracting Ok** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
|
|
104
|
+
| **Extracting Err** | `expectErr(msg)`, `unwrapErr()` |
|
|
105
|
+
| **Transforming** | `map(fn)`, `mapErr(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `flatten()` |
|
|
106
|
+
| **Boolean ops** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)` |
|
|
107
|
+
| **Converting** | `ok()`, `err()`, `transpose()` |
|
|
108
|
+
| **Type casting** | `asOk<F>()`, `asErr<U>()` |
|
|
109
|
+
| **Side effects** | `inspect(fn)`, `inspectErr(fn)` |
|
|
110
|
+
| **Comparison** | `eq(other)` |
|
|
111
|
+
|
|
112
|
+
### Async Methods
|
|
113
|
+
|
|
114
|
+
All transformation methods have async variants with `Async` suffix:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// Async Option methods
|
|
118
|
+
isSomeAndAsync(asyncFn)
|
|
119
|
+
unwrapOrElseAsync(asyncFn)
|
|
120
|
+
andThenAsync(asyncFn)
|
|
121
|
+
orElseAsync(asyncFn)
|
|
122
|
+
|
|
123
|
+
// Async Result methods
|
|
124
|
+
isOkAndAsync(asyncFn)
|
|
125
|
+
isErrAndAsync(asyncFn)
|
|
126
|
+
unwrapOrElseAsync(asyncFn)
|
|
127
|
+
andThenAsync(asyncFn)
|
|
128
|
+
orElseAsync(asyncFn)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Type Aliases
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// Convenient type aliases for common patterns
|
|
135
|
+
type AsyncOption<T> = Promise<Option<T>>;
|
|
136
|
+
type AsyncResult<T, E> = Promise<Result<T, E>>;
|
|
137
|
+
|
|
138
|
+
// For I/O operations
|
|
139
|
+
type IOResult<T> = Result<T, Error>;
|
|
140
|
+
type AsyncIOResult<T> = Promise<IOResult<T>>;
|
|
141
|
+
|
|
142
|
+
// For void returns
|
|
143
|
+
type VoidResult<E> = Result<void, E>;
|
|
144
|
+
type VoidIOResult = IOResult<void>;
|
|
145
|
+
type AsyncVoidResult<E> = Promise<VoidResult<E>>;
|
|
146
|
+
type AsyncVoidIOResult = Promise<VoidIOResult>;
|
|
42
147
|
```
|
|
43
148
|
|
|
44
|
-
|
|
149
|
+
### Utility Functions
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { isOption, isResult, isControlFlow, promiseToAsyncResult } from 'happy-rusty';
|
|
153
|
+
|
|
154
|
+
// Type guards
|
|
155
|
+
if (isOption(value)) { /* ... */ }
|
|
156
|
+
if (isResult(value)) { /* ... */ }
|
|
157
|
+
if (isControlFlow(value)) { /* ... */ }
|
|
158
|
+
|
|
159
|
+
// Convert Promise to Result
|
|
160
|
+
const result = await promiseToAsyncResult(fetch('/api/data'));
|
|
161
|
+
result.inspect(data => console.log(data))
|
|
162
|
+
.inspectErr(err => console.error(err));
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Constants
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { RESULT_TRUE, RESULT_FALSE, RESULT_ZERO, RESULT_VOID } from 'happy-rusty';
|
|
45
169
|
|
|
46
|
-
|
|
170
|
+
// Reusable immutable Result constants
|
|
171
|
+
function validate(): Result<boolean, Error> {
|
|
172
|
+
return isValid ? RESULT_TRUE : RESULT_FALSE;
|
|
173
|
+
}
|
|
47
174
|
|
|
48
|
-
|
|
175
|
+
function doSomething(): Result<void, Error> {
|
|
176
|
+
// ...
|
|
177
|
+
return RESULT_VOID;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Sync Primitives
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { Once, Lazy, LazyAsync, Mutex } from 'happy-rusty';
|
|
185
|
+
|
|
186
|
+
// Once - one-time initialization (like Rust's OnceLock)
|
|
187
|
+
const config = Once<Config>();
|
|
188
|
+
config.set(loadConfig()); // Set once
|
|
189
|
+
config.get(); // Some(config) or None
|
|
190
|
+
config.getOrInit(() => defaultCfg); // Get or initialize
|
|
191
|
+
|
|
192
|
+
// Lazy - lazy initialization with initializer at construction
|
|
193
|
+
const expensive = Lazy(() => computeExpensiveValue());
|
|
194
|
+
expensive.force(); // Compute on first access, cached thereafter
|
|
195
|
+
|
|
196
|
+
// LazyAsync - async lazy initialization
|
|
197
|
+
const db = LazyAsync(async () => await Database.connect(url));
|
|
198
|
+
await db.force(); // Only one connection, concurrent calls wait
|
|
199
|
+
|
|
200
|
+
// Mutex - async mutual exclusion
|
|
201
|
+
const state = Mutex({ count: 0 });
|
|
202
|
+
await state.withLock(async (s) => {
|
|
203
|
+
s.count += 1; // Exclusive access
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Control Flow
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { Break, Continue, ControlFlow } from 'happy-rusty';
|
|
211
|
+
|
|
212
|
+
// Short-circuit operations
|
|
213
|
+
function findFirst<T>(arr: T[], pred: (t: T) => boolean): Option<T> {
|
|
214
|
+
for (const item of arr) {
|
|
215
|
+
const flow = pred(item) ? Break(item) : Continue();
|
|
216
|
+
if (flow.isBreak()) {
|
|
217
|
+
return Some(flow.breakValue().unwrap());
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return None;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Custom fold with early exit
|
|
224
|
+
function tryFold<T, Acc>(
|
|
225
|
+
arr: T[],
|
|
226
|
+
init: Acc,
|
|
227
|
+
f: (acc: Acc, item: T) => ControlFlow<Acc, Acc>
|
|
228
|
+
): Acc {
|
|
229
|
+
let acc = init;
|
|
230
|
+
for (const item of arr) {
|
|
231
|
+
const flow = f(acc, item);
|
|
232
|
+
if (flow.isBreak()) return flow.breakValue().unwrap();
|
|
233
|
+
acc = flow.continueValue().unwrap();
|
|
234
|
+
}
|
|
235
|
+
return acc;
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Examples
|
|
240
|
+
|
|
241
|
+
- [Option basics](examples/option.ts)
|
|
49
242
|
- [AsyncOption](examples/option.async.ts)
|
|
50
|
-
- [Result](examples/result.ts)
|
|
243
|
+
- [Result basics](examples/result.ts)
|
|
51
244
|
- [AsyncResult](examples/result.async.ts)
|
|
245
|
+
- [Once](examples/once.ts)
|
|
246
|
+
- [Lazy](examples/lazy.ts)
|
|
247
|
+
- [Mutex](examples/mutex.ts)
|
|
248
|
+
- [ControlFlow](examples/control_flow.ts)
|
|
249
|
+
|
|
250
|
+
## Documentation
|
|
251
|
+
|
|
252
|
+
Full API documentation is available at [jiangjie.github.io/happy-rusty](https://jiangjie.github.io/happy-rusty/).
|
|
253
|
+
|
|
254
|
+
## Design Notes
|
|
255
|
+
|
|
256
|
+
### Immutability
|
|
257
|
+
|
|
258
|
+
All types (`Option`, `Result`, `ControlFlow`, `Lazy`, `LazyAsync`, `Once`, `Mutex`, `MutexGuard`) are **immutable at runtime** via `Object.freeze()`. This prevents accidental modification of methods or properties:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const some = Some(42);
|
|
262
|
+
some.unwrap = () => 0; // TypeError: Cannot assign to read only property
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Why no `readonly` in TypeScript interfaces?**
|
|
266
|
+
|
|
267
|
+
We intentionally omit `readonly` modifiers from method signatures in interfaces. While this might seem to reduce type safety, there are compelling reasons:
|
|
268
|
+
|
|
269
|
+
1. **Inheritance compatibility** - The `None` type extends `Option<never>`. TypeScript's arrow function property syntax (`readonly prop: () => T`) uses contravariant parameter checking, which causes `None` (with `never` parameters) to be incompatible with `Option<T>`. Method syntax (`method(): T`) uses bivariant checking, allowing the inheritance to work correctly.
|
|
270
|
+
|
|
271
|
+
2. **Runtime protection is sufficient** - `Object.freeze()` already prevents reassignment at runtime. Adding `readonly` only provides compile-time checking, which offers marginal benefit when runtime protection exists.
|
|
272
|
+
|
|
273
|
+
3. **Cleaner API** - Avoiding the `Mutable*` + `Readonly<>` pattern keeps the exported types clean and documentation readable.
|
|
274
|
+
|
|
275
|
+
4. **Testing validates immutability** - Our test suite explicitly verifies that all instances are frozen and reject property modifications.
|
|
276
|
+
|
|
277
|
+
## Why happy-rusty?
|
|
278
|
+
|
|
279
|
+
JavaScript's `null`/`undefined` and try-catch patterns lead to:
|
|
280
|
+
- Uncaught null reference errors
|
|
281
|
+
- Forgotten error handling
|
|
282
|
+
- Verbose try-catch blocks
|
|
283
|
+
- Unclear function contracts
|
|
284
|
+
|
|
285
|
+
`happy-rusty` provides Rust's battle-tested patterns:
|
|
286
|
+
- **Explicit optionality** - `Option<T>` makes absence visible in types
|
|
287
|
+
- **Explicit errors** - `Result<T, E>` forces error handling consideration
|
|
288
|
+
- **Method chaining** - Transform values without nested if-else or try-catch
|
|
289
|
+
- **Type safety** - Full TypeScript support with strict type inference
|
|
290
|
+
|
|
291
|
+
## License
|
|
52
292
|
|
|
53
|
-
|
|
293
|
+
[GPL-3.0](LICENSE)
|