happy-rusty 1.9.2 → 1.10.1

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 CHANGED
@@ -5,46 +5,83 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.10.1] - 2026-09-06
9
+
10
+ ### Fixed
11
+
12
+ - **Channel**: `for await` leaving early (a `break`, an error thrown in the loop body, or an explicit `iterator.return()`) now cancels the pending `next()`. An abandoned iterator no longer holds its spot in the receive queue, so it cannot swallow a message intended for another consumer
13
+ - **Channel**: a timeout callback firing after its waiter was already served no longer removes an unrelated waiter (`splice(-1, 1)` on a `-1` index)
14
+ - **OnceAsync**: `set()` and `tryInsert()` return `Err` while a `getOrInit`/`getOrTryInit` attempt is in flight, instead of reporting `Ok` and then being overwritten by the attempt's result — matching Rust's `OnceLock::set` racing an in-progress `get_or_init`
15
+ - **OnceAsync**: `wait()` no longer rejects when an initialization attempt fails. It keeps waiting until the cell is actually initialized, matching Rust's `OnceLock::wait`
16
+ - **OnceAsync**: a caller joining a failed attempt retries with its own initializer, matching Rust's `get_or_try_init`. The in-flight slot is claimed synchronously, so a synchronously-executing initializer that re-enters joins the attempt instead of starting a second one
17
+
18
+ ### Chores
19
+
20
+ - Migrated to the Vite+ unified toolchain (`vp check` for fmt + lint + type checking, `vp pack` for JS and `.d.ts` bundling), replacing ESLint and the previous build setup
21
+ - Merged all CI gates into a single `ci.yml` job and added attw + publint verification of the tarball that is actually published
22
+ - Upgraded devDependencies: `vite-plus` 0.3.0, `@vitest/coverage-v8` 4.1.11, `publint` 0.3.24
23
+
24
+ ## [1.10.0] - 2026-06-30
25
+
26
+ ### Added
27
+
28
+ - **New Sync Primitive**: `Semaphore` - Counting semaphore for limiting async concurrency, with `acquire`/`tryAcquire`/`withPermit`/`availablePermits`. Inspired by tokio's `Semaphore` (Rust std does not include one). Useful for fetch rate limiting, connection pools, and task queues.
29
+ - **New Method**: `RwLockWriteGuard.downgrade()` - Atomically converts a write guard to a read guard, releasing waiting readers while keeping pending writers waiting. Equivalent to Rust 1.92's `RwLockWriteGuard::downgrade`
30
+
31
+ ### Chores
32
+
33
+ - Upgraded devDependencies: vite, vitest, @vitest/coverage-v8, @vitest/ui, eslint, rollup, typescript-eslint
34
+
8
35
  ## [1.9.2] - 2026-04-12
9
36
 
10
37
  ### Changed
38
+
11
39
  - **Performance**: Added `/*#__PURE__*/` annotations to all top-level function call expressions for better tree-shaking support in consumer bundlers
12
40
 
13
41
  ### Fixed
42
+
14
43
  - **Build**: Migrated `treeshake` config from deprecated `'smallest'` string to object form for Vite 8 (Rolldown) compatibility
15
44
  - **Build**: Added `topLevelVar: false` to output options to preserve `const` declarations in bundled output
16
45
 
17
46
  ### Chores
47
+
18
48
  - Upgraded devDependencies: vite, vitest, eslint, typedoc, typescript-eslint
19
49
 
20
50
  ## [1.9.1] - 2026-01-16
21
51
 
22
52
  ### Changed
53
+
23
54
  - **Performance**: `Channel` buffer replaced `Array` with `Queue` for O(1) shift operations
24
55
 
25
56
  ### Documentation
57
+
26
58
  - Added `@since` tags to all public APIs
27
59
  - Improved JSDoc consistency and formatting
28
60
 
29
61
  ### CI
62
+
30
63
  - Changed JSR publish to trigger on release instead of push to main
31
64
 
32
65
  ## [1.9.0] - 2026-01-04
33
66
 
34
67
  ### Added
68
+
35
69
  - **Try Extensions for Result**:
36
70
  - `andTryAsync<U>` - Like `andThenAsync`, but auto-catches exceptions/Promise rejections and converts them to `Err`
37
71
  - `orTryAsync<F>` - Like `orElseAsync`, but auto-catches exceptions in recovery logic
38
72
 
39
73
  ### Changed
74
+
40
75
  - **Performance**: `map()` and `mapErr()` now return `this` when the Result variant doesn't match, avoiding unnecessary object creation
41
76
 
42
77
  ### Removed
78
+
43
79
  - `promiseToAsyncResult()` - Use `tryAsyncResult()` instead (deprecated since v1.7.0)
44
80
 
45
81
  ## [1.8.0] - 2025-12-31
46
82
 
47
83
  ### Added
84
+
48
85
  - **New Sync Primitives**:
49
86
  - `OnceAsync<T>` - Async-first one-time initialization with concurrent call handling
50
87
  - `RwLock<T>` - Async read-write lock (multiple concurrent readers or single writer)
@@ -69,18 +106,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
69
106
  - **Rust Documentation Links** - All Rust-equivalent interfaces now link to official Rust docs via `@see` tags
70
107
 
71
108
  ### Changed
109
+
72
110
  - **Module Reorganization** - Source code restructured into `core/` (Option, Result) and `std/` (sync, ops) directories, mirroring Rust's stdlib organization
73
111
  - **Async Callback Types** - All async methods now accept `PromiseLike<T> | T` instead of just `Promise<T>`, allowing mixed sync/async callbacks
74
112
  - **Async Return Types** - Use `Awaited<T>` to properly flatten nested Promises in async method return types
75
113
  - **Internal Optimizations** - Cached Promise constants (`ASYNC_NONE`, etc.) for better runtime performance
76
114
 
77
115
  ### Fixed
116
+
78
117
  - `this` binding issue in `Once.getOrTryInitAsync`
79
118
  - Nested Promise flattening in async methods
80
119
 
81
120
  ## [1.7.1] - 2025-12-26
82
121
 
83
122
  ### Added
123
+
84
124
  - **Argument passing** - All `try*` functions now support passing arguments like `Promise.try`:
85
125
  - `tryOption(JSON.parse, jsonString)` instead of `tryOption(() => JSON.parse(jsonString))`
86
126
  - `tryResult(decodeURIComponent, str)` instead of `tryResult(() => decodeURIComponent(str))`
@@ -89,35 +129,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
89
129
  ## [1.7.0] - 2025-12-26
90
130
 
91
131
  ### Added
132
+
92
133
  - `tryOption()` - Capture synchronous exceptions as Option (success → Some, exception → None)
93
134
  - `tryAsyncOption()` - Capture async/Promise exceptions as Option
94
135
  - `tryResult()` - Capture synchronous exceptions as Result (success → Ok, exception → Err)
95
136
  - `tryAsyncResult()` - Capture async/Promise exceptions as Result
96
137
 
97
138
  ### Deprecated
139
+
98
140
  - `promiseToAsyncResult()` - Use `tryAsyncResult()` instead (same functionality, more consistent naming)
99
141
 
100
142
  ## [1.6.2] - 2025-12-25
101
143
 
102
144
  ### Changed
145
+
103
146
  - `promiseToAsyncResult` now accepts `PromiseLike<T>` instead of `Promise<T>` for broader compatibility with thenable objects
104
147
  - Use `AsyncResult` type alias in function signatures for consistency
105
148
 
106
149
  ### Removed
150
+
107
151
  - Removed unused `source` field from package.json (Parcel legacy)
108
152
  - Removed redundant `@internal` tags from private (non-exported) functions
109
153
 
110
154
  ## [1.6.1] - 2025-12-19
111
155
 
112
156
  ### Changed
157
+
113
158
  - License changed from GPL-3.0 to MIT for broader adoption and easier integration
114
159
 
115
160
  ### Added
161
+
116
162
  - Navigation links (GitHub, npm, JSR) in TypeDoc documentation header
117
163
 
118
164
  ## [1.6.0] - 2025-12-18
119
165
 
120
166
  ### Added
167
+
121
168
  - **Sync Primitives**: `Once<T>`, `Lazy<T>`, `LazyAsync<T>`, `Mutex<T>` for Rust-style synchronization
122
169
  - **Control Flow**: `ControlFlow<B, C>` with `Break` and `Continue` variants
123
170
  - `Symbol.toStringTag` property to all types for better type identification
@@ -131,28 +178,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
131
178
  - CHANGELOG.md with full version history
132
179
 
133
180
  ### Changed
181
+
134
182
  - All instances are now frozen with `Object.freeze()` for runtime immutability
135
183
  - TypeDoc output format from Markdown to HTML for GitHub Pages compatibility
136
184
  - Migrated from Deno test to Vitest
137
185
  - Build process split into Vite (JS) and Rollup (dts)
138
186
 
139
187
  ### Fixed
188
+
140
189
  - Symbol property syntax and duplicate declaration issues
141
190
  - Type inference improvements for `None` interface methods
142
191
 
143
192
  ## [1.5.0] - 2024-08-11
144
193
 
145
194
  ### Added
195
+
146
196
  - Async versions of methods: `isSomeAndAsync`, `isOkAndAsync`, `isErrAndAsync`, `unwrapOrElseAsync`, `andThenAsync`, `orElseAsync`
147
197
  - Async examples
148
198
 
149
199
  ### Changed
200
+
150
201
  - Updated pnpm to v9.7.0
151
202
  - Updated ESLint configuration
152
203
 
153
204
  ## [1.4.0] - 2024-08-05
154
205
 
155
206
  ### Added
207
+
156
208
  - `Ok()` constructor without arguments (similar to Rust's `Ok(())`)
157
209
  - `RESULT_VOID` constant for void Result returns
158
210
  - `VoidResult<E>`, `VoidIOResult`, `AsyncVoidResult<E>`, `AsyncVoidIOResult` type aliases
@@ -160,94 +212,114 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
160
212
  ## [1.3.2] - 2024-08-04
161
213
 
162
214
  ### Changed
215
+
163
216
  - Renamed `helpers` to `utils`
164
217
  - Improved `@example` annotations in JSDoc
165
218
 
166
219
  ## [1.3.1] - 2024-08-03
167
220
 
168
221
  ### Fixed
222
+
169
223
  - `map` methods now correctly return new objects instead of mutating
170
224
 
171
225
  ### Changed
226
+
172
227
  - Updated Rollup to v4.20.0
173
228
 
174
229
  ## [1.3.0] - 2024-08-01
175
230
 
176
231
  ### Added
232
+
177
233
  - `RESULT_TRUE`, `RESULT_FALSE`, `RESULT_ZERO` constants
178
234
 
179
235
  ### Fixed
236
+
180
237
  - Circular dependency issues
181
238
 
182
239
  ### Changed
240
+
183
241
  - Reorganized code structure
184
242
 
185
243
  ## [1.2.0] - 2024-08-01
186
244
 
187
245
  ### Added
246
+
188
247
  - `isOption()` and `isResult()` type guard utilities
189
248
  - Custom string conversion for Option and Result
190
249
 
191
250
  ### Changed
251
+
192
252
  - Replaced arrow functions with normal functions for better debug experience
193
253
  - Updated ESLint to v9
194
254
 
195
255
  ## [1.1.2] - 2024-07-17
196
256
 
197
257
  ### Added
258
+
198
259
  - `asOk<F>()` and `asErr<U>()` methods for type casting
199
260
 
200
261
  ## [1.1.1] - 2024-07-13
201
262
 
202
263
  ### Fixed
264
+
203
265
  - TypeDoc configuration issues
204
266
 
205
267
  ### Changed
268
+
206
269
  - Updated dependencies
207
270
 
208
271
  ## [1.1.0] - 2024-06-09
209
272
 
210
273
  ### Added
274
+
211
275
  - `Option.filter()` method
212
276
  - Many new Option and Result APIs
213
277
  - Comprehensive code comments and documentation
214
278
 
215
279
  ### Changed
280
+
216
281
  - Improved type inference
217
282
  - `Option.filter()` now returns `Option<T>` instead of boolean
218
283
 
219
284
  ## [1.0.9] - 2024-05-14
220
285
 
221
286
  ### Added
287
+
222
288
  - Documentation for exported symbols
223
289
  - README examples
224
290
 
225
291
  ## [1.0.8] - 2024-05-13
226
292
 
227
293
  ### Changed
294
+
228
295
  - Switched from npm to pnpm
229
296
  - Added GitHub Actions test workflow with Codecov
230
297
 
231
298
  ## [1.0.7] - 2024-05-08
232
299
 
233
300
  ### Changed
301
+
234
302
  - `Some` value type changed to `NonNullable<T>`
235
303
 
236
304
  ### Fixed
305
+
237
306
  - Can now invoke `err()` from `Ok` variant
238
307
 
239
308
  ## [1.0.6] - 2024-05-08
240
309
 
241
310
  ### Added
311
+
242
312
  - Commonly used type exports
243
313
 
244
314
  ### Changed
315
+
245
316
  - Set `type: "module"` in package.json
246
317
  - Build target set to ESNext
247
318
 
248
319
  ## [1.0.5] - 2024-05-05
249
320
 
250
321
  ### Changed
322
+
251
323
  - Throw `TypeError` instead of generic `Error`
252
324
  - Switched to Rollup for building
253
325
  - Added Bun support in CI
@@ -255,45 +327,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
255
327
  ## [1.0.4] - 2024-05-04
256
328
 
257
329
  ### Added
330
+
258
331
  - Chinese README (`README.cn.md`)
259
332
  - JSR publishing workflow
260
333
  - npm publishing workflows
261
334
 
262
335
  ### Changed
336
+
263
337
  - Replaced Parcel with Rollup for building
264
338
  - Replaced Jest with Bun test
265
339
 
266
340
  ## [1.0.3] - 2024-04-27
267
341
 
268
342
  ### Added
343
+
269
344
  - Installation and Examples sections in README
270
345
 
271
346
  ### Changed
347
+
272
348
  - Force return const for better type inference
273
349
 
274
350
  ## [1.0.2] - 2024-04-26
275
351
 
276
352
  ### Added
353
+
277
354
  - JSR publishing support
278
355
 
279
356
  ### Changed
357
+
280
358
  - Improved code comments
281
359
 
282
360
  ## [1.0.1] - 2024-04-26
283
361
 
284
362
  ### Changed
363
+
285
364
  - Replaced enum with const for better tree-shaking
286
365
  - Marked package as side-effect free
287
366
 
288
367
  ## [1.0.0] - 2024-04-24
289
368
 
290
369
  ### Added
370
+
291
371
  - Initial release
292
372
  - `Option<T>` type with `Some` and `None` variants
293
373
  - `Result<T, E>` type with `Ok` and `Err` variants
294
374
  - Full TypeScript support
295
375
  - Comprehensive API matching Rust's Option and Result
296
376
 
377
+ [1.10.1]: https://github.com/JiangJie/happy-rusty/compare/v1.10.0...v1.10.1
378
+ [1.10.0]: https://github.com/JiangJie/happy-rusty/compare/v1.9.2...v1.10.0
297
379
  [1.9.2]: https://github.com/JiangJie/happy-rusty/compare/v1.9.1...v1.9.2
298
380
  [1.9.1]: https://github.com/JiangJie/happy-rusty/compare/v1.9.0...v1.9.1
299
381
  [1.9.0]: https://github.com/JiangJie/happy-rusty/compare/v1.8.0...v1.9.0
package/README.cn.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # happy-rusty
2
2
 
3
3
  [![License](https://img.shields.io/npm/l/happy-rusty.svg)](LICENSE)
4
- [![Build Status](https://github.com/JiangJie/happy-rusty/actions/workflows/test.yml/badge.svg)](https://github.com/JiangJie/happy-rusty/actions/workflows/test.yml)
4
+ [![Build Status](https://github.com/JiangJie/happy-rusty/actions/workflows/ci.yml/badge.svg)](https://github.com/JiangJie/happy-rusty/actions/workflows/ci.yml)
5
5
  [![codecov](https://codecov.io/gh/JiangJie/happy-rusty/graph/badge.svg)](https://codecov.io/gh/JiangJie/happy-rusty)
6
6
  [![NPM version](https://img.shields.io/npm/v/happy-rusty.svg)](https://npmjs.org/package/happy-rusty)
7
7
  [![NPM downloads](https://badgen.net/npm/dm/happy-rusty)](https://npmjs.org/package/happy-rusty)
@@ -84,37 +84,39 @@ const config = parseJSON<Config>(jsonStr)
84
84
 
85
85
  ### Option&lt;T&gt;
86
86
 
87
- | 分类 | 方法 |
88
- |------|------|
89
- | **构造器** | `Some(value)`, `None` |
90
- | **查询** | `isSome()`, `isNone()`, `isSomeAnd(fn)` |
91
- | **提取** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
92
- | **转换** | `map(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `filter(fn)`, `flatten()` |
93
- | **布尔操作** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)`, `xor(other)` |
94
- | **类型转换** | `okOr(err)`, `okOrElse(fn)`, `transpose()` |
95
- | **组合** | `zip(other)`, `zipWith(other, fn)`, `unzip()` |
96
- | **副作用** | `inspect(fn)` |
97
- | **比较** | `eq(other)` |
87
+ | 分类 | 方法 |
88
+ | ------------ | -------------------------------------------------------------------------------------- |
89
+ | **构造器** | `Some(value)`, `None` |
90
+ | **查询** | `isSome()`, `isNone()`, `isSomeAnd(fn)` |
91
+ | **提取** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
92
+ | **转换** | `map(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `filter(fn)`, `flatten()` |
93
+ | **布尔操作** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)`, `xor(other)` |
94
+ | **类型转换** | `okOr(err)`, `okOrElse(fn)`, `transpose()` |
95
+ | **组合** | `zip(other)`, `zipWith(other, fn)`, `unzip()` |
96
+ | **副作用** | `inspect(fn)` |
97
+ | **比较** | `eq(other)` |
98
98
 
99
99
  ### Result&lt;T, E&gt;
100
100
 
101
- | 分类 | 方法 |
102
- |------|------|
103
- | **构造器** | `Ok(value)`, `Ok()` (void), `Err(error)` |
104
- | **查询** | `isOk()`, `isErr()`, `isOkAnd(fn)`, `isErrAnd(fn)` |
105
- | **提取 Ok** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
106
- | **提取 Err** | `expectErr(msg)`, `unwrapErr()` |
107
- | **转换** | `map(fn)`, `mapErr(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `flatten()` |
108
- | **布尔操作** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)` |
109
- | **类型转换** | `ok()`, `err()`, `transpose()` |
110
- | **类型断言** | `asOk<F>()`, `asErr<U>()` |
111
- | **副作用** | `inspect(fn)`, `inspectErr(fn)` |
112
- | **比较** | `eq(other)` |
101
+ | 分类 | 方法 |
102
+ | ------------ | -------------------------------------------------------------------------------------- |
103
+ | **构造器** | `Ok(value)`, `Ok()` (void), `Err(error)` |
104
+ | **查询** | `isOk()`, `isErr()`, `isOkAnd(fn)`, `isErrAnd(fn)` |
105
+ | **提取 Ok** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
106
+ | **提取 Err** | `expectErr(msg)`, `unwrapErr()` |
107
+ | **转换** | `map(fn)`, `mapErr(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `flatten()` |
108
+ | **布尔操作** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)` |
109
+ | **类型转换** | `ok()`, `err()`, `transpose()` |
110
+ | **类型断言** | `asOk<F>()`, `asErr<U>()` |
111
+ | **副作用** | `inspect(fn)`, `inspectErr(fn)` |
112
+ | **比较** | `eq(other)` |
113
113
 
114
114
  ### 异步方法
115
115
 
116
116
  所有转换方法都有带 `Async` 后缀的异步变体(如 `andThenAsync`、`mapAsync`、`unwrapOrElseAsync`)。
117
117
 
118
+ 异步变体遵循库的错误约定:回调被实际调用时,若其同步抛错,错误会在调用点同步抛出——与同步方法一致;只有回调返回 rejected Promise 时,错误才以 rejection 形式呈现。如需把抛出的异常转为 `Err`/`None` 值,请使用 `try*` 辅助函数(`tryResult`/`tryAsyncResult`/`tryOption`/`tryAsyncOption`),或在 `Result` 上使用 `andTryAsync`/`orTryAsync` 方法。
119
+
118
120
  ### 类型别名
119
121
 
120
122
  ```ts
@@ -195,12 +197,14 @@ some.unwrap = () => 0; // TypeError: Cannot assign to read only property
195
197
  ## 为什么选择 happy-rusty?
196
198
 
197
199
  JavaScript 的 `null`/`undefined` 和 try-catch 模式会导致:
200
+
198
201
  - 未捕获的空引用错误
199
202
  - 遗忘的错误处理
200
203
  - 冗长的 try-catch 代码块
201
204
  - 不清晰的函数契约
202
205
 
203
206
  `happy-rusty` 提供了 Rust 久经考验的模式:
207
+
204
208
  - **显式可选性** - `Option<T>` 在类型中明确表示值的缺失
205
209
  - **显式错误** - `Result<T, E>` 强制考虑错误处理
206
210
  - **链式调用** - 无需嵌套 if-else 或 try-catch 即可转换值
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # happy-rusty
2
2
 
3
3
  [![License](https://img.shields.io/npm/l/happy-rusty.svg)](LICENSE)
4
- [![Build Status](https://github.com/JiangJie/happy-rusty/actions/workflows/test.yml/badge.svg)](https://github.com/JiangJie/happy-rusty/actions/workflows/test.yml)
4
+ [![Build Status](https://github.com/JiangJie/happy-rusty/actions/workflows/ci.yml/badge.svg)](https://github.com/JiangJie/happy-rusty/actions/workflows/ci.yml)
5
5
  [![codecov](https://codecov.io/gh/JiangJie/happy-rusty/graph/badge.svg)](https://codecov.io/gh/JiangJie/happy-rusty)
6
6
  [![NPM version](https://img.shields.io/npm/v/happy-rusty.svg)](https://npmjs.org/package/happy-rusty)
7
7
  [![NPM downloads](https://badgen.net/npm/dm/happy-rusty)](https://npmjs.org/package/happy-rusty)
@@ -20,7 +20,7 @@ Rust's `Option`, `Result`, and sync primitives for JavaScript/TypeScript - Bette
20
20
 
21
21
  - **Option&lt;T&gt;** - Represents an optional value: every `Option` is either `Some(T)` or `None`
22
22
  - **Result&lt;T, E&gt;** - Represents either success (`Ok(T)`) or failure (`Err(E)`)
23
- - **Sync Primitives** - Rust-inspired `Once<T>`, `OnceAsync<T>`, `Lazy<T>`, `LazyAsync<T>`, `Mutex<T>`, `RwLock<T>`, and `Channel<T>`
23
+ - **Sync Primitives** - Rust-inspired `Once<T>`, `OnceAsync<T>`, `Lazy<T>`, `LazyAsync<T>`, `Mutex<T>`, `RwLock<T>`, `Semaphore`, and `Channel<T>`
24
24
  - **Control Flow** - `ControlFlow<B, C>` with `Break` and `Continue` for short-circuiting operations
25
25
  - **FnOnce** - One-time callable function wrappers (`FnOnce` and `FnOnceAsync`)
26
26
  - **Full TypeScript support** with strict type inference
@@ -84,37 +84,39 @@ const config = parseJSON<Config>(jsonStr)
84
84
 
85
85
  ### Option&lt;T&gt;
86
86
 
87
- | Category | Methods |
88
- |----------|---------|
89
- | **Constructors** | `Some(value)`, `None` |
90
- | **Querying** | `isSome()`, `isNone()`, `isSomeAnd(fn)` |
91
- | **Extracting** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
87
+ | Category | Methods |
88
+ | ---------------- | -------------------------------------------------------------------------------------- |
89
+ | **Constructors** | `Some(value)`, `None` |
90
+ | **Querying** | `isSome()`, `isNone()`, `isSomeAnd(fn)` |
91
+ | **Extracting** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
92
92
  | **Transforming** | `map(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `filter(fn)`, `flatten()` |
93
- | **Boolean ops** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)`, `xor(other)` |
94
- | **Converting** | `okOr(err)`, `okOrElse(fn)`, `transpose()` |
95
- | **Combining** | `zip(other)`, `zipWith(other, fn)`, `unzip()` |
96
- | **Side effects** | `inspect(fn)` |
97
- | **Comparison** | `eq(other)` |
93
+ | **Boolean ops** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)`, `xor(other)` |
94
+ | **Converting** | `okOr(err)`, `okOrElse(fn)`, `transpose()` |
95
+ | **Combining** | `zip(other)`, `zipWith(other, fn)`, `unzip()` |
96
+ | **Side effects** | `inspect(fn)` |
97
+ | **Comparison** | `eq(other)` |
98
98
 
99
99
  ### Result&lt;T, E&gt;
100
100
 
101
- | Category | Methods |
102
- |----------|---------|
103
- | **Constructors** | `Ok(value)`, `Ok()` (void), `Err(error)` |
104
- | **Querying** | `isOk()`, `isErr()`, `isOkAnd(fn)`, `isErrAnd(fn)` |
105
- | **Extracting Ok** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
106
- | **Extracting Err** | `expectErr(msg)`, `unwrapErr()` |
107
- | **Transforming** | `map(fn)`, `mapErr(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `flatten()` |
108
- | **Boolean ops** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)` |
109
- | **Converting** | `ok()`, `err()`, `transpose()` |
110
- | **Type casting** | `asOk<F>()`, `asErr<U>()` |
111
- | **Side effects** | `inspect(fn)`, `inspectErr(fn)` |
112
- | **Comparison** | `eq(other)` |
101
+ | Category | Methods |
102
+ | ------------------ | -------------------------------------------------------------------------------------- |
103
+ | **Constructors** | `Ok(value)`, `Ok()` (void), `Err(error)` |
104
+ | **Querying** | `isOk()`, `isErr()`, `isOkAnd(fn)`, `isErrAnd(fn)` |
105
+ | **Extracting Ok** | `expect(msg)`, `unwrap()`, `unwrapOr(default)`, `unwrapOrElse(fn)` |
106
+ | **Extracting Err** | `expectErr(msg)`, `unwrapErr()` |
107
+ | **Transforming** | `map(fn)`, `mapErr(fn)`, `mapOr(default, fn)`, `mapOrElse(defaultFn, fn)`, `flatten()` |
108
+ | **Boolean ops** | `and(other)`, `andThen(fn)`, `or(other)`, `orElse(fn)` |
109
+ | **Converting** | `ok()`, `err()`, `transpose()` |
110
+ | **Type casting** | `asOk<F>()`, `asErr<U>()` |
111
+ | **Side effects** | `inspect(fn)`, `inspectErr(fn)` |
112
+ | **Comparison** | `eq(other)` |
113
113
 
114
114
  ### Async Methods
115
115
 
116
116
  All transformation methods have async variants with `Async` suffix (e.g., `andThenAsync`, `mapAsync`, `unwrapOrElseAsync`).
117
117
 
118
+ Async variants follow the library's error convention: when the callback is invoked, a synchronous throw propagates synchronously at the call site — just like the sync methods — and only a callback that returns a rejected promise surfaces as a promise rejection. To capture thrown exceptions as `Err`/`None` values instead, use the `try*` helpers (`tryResult`/`tryAsyncResult`/`tryOption`/`tryAsyncOption`) or, on `Result`, the `andTryAsync`/`orTryAsync` methods.
119
+
118
120
  ### Type Aliases
119
121
 
120
122
  ```ts
@@ -137,7 +139,7 @@ const response = await tryAsyncResult(fetch, '/api/data');
137
139
  ### Sync Primitives
138
140
 
139
141
  ```ts
140
- import { Lazy, LazyAsync, Mutex, Channel } from 'happy-rusty';
142
+ import { Lazy, LazyAsync, Mutex, Semaphore, Channel } from 'happy-rusty';
141
143
 
142
144
  // Lazy - compute once on first access
143
145
  const expensive = Lazy(() => computeExpensiveValue());
@@ -151,6 +153,10 @@ await db.force(); // Only one connection, concurrent calls wait
151
153
  const state = Mutex({ count: 0 });
152
154
  await state.withLock(async (s) => { s.count += 1; });
153
155
 
156
+ // Semaphore - limit async concurrency (e.g. fetch rate limiting)
157
+ const sem = Semaphore(10);
158
+ await sem.withPermit(() => fetch(url));
159
+
154
160
  // Channel - MPMC async message passing
155
161
  const ch = Channel<string>(10); // bounded capacity
156
162
  await ch.send('hello');
@@ -165,6 +171,7 @@ for await (const msg of ch) { console.log(msg); }
165
171
  - [Lazy](examples/std/sync/lazy.ts) / [LazyAsync](examples/std/sync/lazy_async.ts)
166
172
  - [Mutex](examples/std/sync/mutex.ts)
167
173
  - [RwLock](examples/std/sync/rwlock.ts)
174
+ - [Semaphore](examples/std/sync/semaphore.ts)
168
175
  - [Channel](examples/std/sync/channel.ts)
169
176
  - [ControlFlow](examples/std/ops/control_flow.ts)
170
177
  - [FnOnce](examples/std/ops/fn_once.ts) / [FnOnceAsync](examples/std/ops/fn_once_async.ts)
@@ -173,7 +180,7 @@ for await (const msg of ch) { console.log(msg); }
173
180
 
174
181
  ### Immutability
175
182
 
176
- All types (`Option`, `Result`, `ControlFlow`, `Lazy`, `LazyAsync`, `Once`, `OnceAsync`, `Mutex`, `MutexGuard`, `RwLock`, `Channel`, `Sender`, `Receiver`, `FnOnce`, `FnOnceAsync`) are **immutable at runtime** via `Object.freeze()`. This prevents accidental modification of methods or properties:
183
+ All types (`Option`, `Result`, `ControlFlow`, `Lazy`, `LazyAsync`, `Once`, `OnceAsync`, `Mutex`, `MutexGuard`, `RwLock`, `Semaphore`, `SemaphorePermit`, `Channel`, `Sender`, `Receiver`, `FnOnce`, `FnOnceAsync`) are **immutable at runtime** via `Object.freeze()`. This prevents accidental modification of methods or properties:
177
184
 
178
185
  ```ts
179
186
  const some = Some(42);
@@ -195,12 +202,14 @@ We intentionally omit `readonly` modifiers from method signatures in interfaces.
195
202
  ## Why happy-rusty?
196
203
 
197
204
  JavaScript's `null`/`undefined` and try-catch patterns lead to:
205
+
198
206
  - Uncaught null reference errors
199
207
  - Forgotten error handling
200
208
  - Verbose try-catch blocks
201
209
  - Unclear function contracts
202
210
 
203
211
  `happy-rusty` provides Rust's battle-tested patterns:
212
+
204
213
  - **Explicit optionality** - `Option<T>` makes absence visible in types
205
214
  - **Explicit errors** - `Result<T, E>` forces error handling consideration
206
215
  - **Method chaining** - Transform values without nested if-else or try-catch