happy-rusty 1.9.2 → 1.10.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 +10 -0
- package/README.md +8 -3
- package/dist/main.cjs +138 -13
- package/dist/main.cjs.map +1 -1
- package/dist/main.mjs +138 -14
- package/dist/main.mjs.map +1 -1
- package/dist/types.d.ts +299 -2
- package/package.json +10 -10
package/dist/types.d.ts
CHANGED
|
@@ -4627,6 +4627,37 @@ interface RwLockWriteGuard<T> {
|
|
|
4627
4627
|
* ```
|
|
4628
4628
|
*/
|
|
4629
4629
|
unlock(): void;
|
|
4630
|
+
/**
|
|
4631
|
+
* Downgrades this write guard to a read guard atomically.
|
|
4632
|
+
*
|
|
4633
|
+
* The write lock is converted to a read lock without releasing it,
|
|
4634
|
+
* allowing other waiting readers to proceed concurrently. Pending
|
|
4635
|
+
* writers continue to wait until all readers (including this one)
|
|
4636
|
+
* release their locks.
|
|
4637
|
+
*
|
|
4638
|
+
* After calling `downgrade()`, this guard is invalidated and must not
|
|
4639
|
+
* be used — accessing `value` or calling `unlock()` will throw.
|
|
4640
|
+
*
|
|
4641
|
+
* Equivalent to Rust's `RwLockWriteGuard::downgrade` (stabilized in
|
|
4642
|
+
* Rust 1.92.0).
|
|
4643
|
+
*
|
|
4644
|
+
* @returns A new `RwLockReadGuard<T>` providing shared read access.
|
|
4645
|
+
* @since 1.10.0
|
|
4646
|
+
* @see https://doc.rust-lang.org/std/sync/struct.RwLockWriteGuard.html#method.downgrade
|
|
4647
|
+
* @example
|
|
4648
|
+
* ```ts
|
|
4649
|
+
* const guard = await rwlock.write();
|
|
4650
|
+
* guard.value = newValue;
|
|
4651
|
+
* // Downgrade to a read lock, releasing waiting readers
|
|
4652
|
+
* const readGuard = guard.downgrade();
|
|
4653
|
+
* try {
|
|
4654
|
+
* console.log(readGuard.value); // other readers can proceed concurrently
|
|
4655
|
+
* } finally {
|
|
4656
|
+
* readGuard.unlock();
|
|
4657
|
+
* }
|
|
4658
|
+
* ```
|
|
4659
|
+
*/
|
|
4660
|
+
downgrade(): RwLockReadGuard<T>;
|
|
4630
4661
|
}
|
|
4631
4662
|
/**
|
|
4632
4663
|
* An async read-write lock for protecting shared data.
|
|
@@ -4903,5 +4934,271 @@ interface RwLock<T> {
|
|
|
4903
4934
|
*/
|
|
4904
4935
|
declare function RwLock<T>(value: T): RwLock<T>;
|
|
4905
4936
|
|
|
4906
|
-
|
|
4907
|
-
|
|
4937
|
+
/**
|
|
4938
|
+
* @module
|
|
4939
|
+
* Counting semaphore for limiting async concurrency.
|
|
4940
|
+
*
|
|
4941
|
+
* Inspired by [tokio's `Semaphore`](https://docs.rs/tokio/latest/tokio/sync/struct.Semaphore.html)
|
|
4942
|
+
* (Rust std does not include one). Unlike `Mutex<T>` which binds to a value,
|
|
4943
|
+
* `Semaphore` is a pure concurrency counter: it limits how many async
|
|
4944
|
+
* operations can run concurrently without protecting any data.
|
|
4945
|
+
*
|
|
4946
|
+
* **When to use `Semaphore` vs `Mutex<T>`:**
|
|
4947
|
+
* - Use `Mutex<T>` for exclusive access to a value (n=1, with data)
|
|
4948
|
+
* - Use `Semaphore` to limit concurrency to N (e.g. fetch rate limiting,
|
|
4949
|
+
* connection pools, task queues)
|
|
4950
|
+
*
|
|
4951
|
+
* `Semaphore(1)` behaves like a `Mutex` without a value (a binary semaphore),
|
|
4952
|
+
* but `Mutex<T>` is preferred when you need to protect a value since the
|
|
4953
|
+
* guard provides typed access via `value`.
|
|
4954
|
+
*/
|
|
4955
|
+
|
|
4956
|
+
/**
|
|
4957
|
+
* A permit acquired from a {@link Semaphore}.
|
|
4958
|
+
*
|
|
4959
|
+
* The permit must be released after use by calling `release()`. Failure to
|
|
4960
|
+
* release reduces available concurrency until the permit is garbage collected
|
|
4961
|
+
* (JavaScript has no RAII like Rust's `Drop`).
|
|
4962
|
+
*
|
|
4963
|
+
* Prefer {@link Semaphore.withPermit} for automatic acquire/release with
|
|
4964
|
+
* `try/finally` semantics. Manual `acquire()`/`release()` requires a
|
|
4965
|
+
* `try/finally` block to avoid leaking permits on exceptions.
|
|
4966
|
+
*
|
|
4967
|
+
* @since 1.10.0
|
|
4968
|
+
* @see {@link Semaphore}
|
|
4969
|
+
* @example
|
|
4970
|
+
* ```ts
|
|
4971
|
+
* const sem = Semaphore(2);
|
|
4972
|
+
* const permit = await sem.acquire();
|
|
4973
|
+
* try {
|
|
4974
|
+
* await doWork();
|
|
4975
|
+
* } finally {
|
|
4976
|
+
* permit.release();
|
|
4977
|
+
* }
|
|
4978
|
+
* ```
|
|
4979
|
+
*/
|
|
4980
|
+
interface SemaphorePermit {
|
|
4981
|
+
/**
|
|
4982
|
+
* The well-known symbol `Symbol.toStringTag` used by `Object.prototype.toString()`.
|
|
4983
|
+
* Returns `'SemaphorePermit'` so that `Object.prototype.toString.call(permit)`
|
|
4984
|
+
* produces `'[object SemaphorePermit]'`.
|
|
4985
|
+
*/
|
|
4986
|
+
readonly [Symbol.toStringTag]: 'SemaphorePermit';
|
|
4987
|
+
/**
|
|
4988
|
+
* Custom `toString` implementation.
|
|
4989
|
+
*
|
|
4990
|
+
* @example
|
|
4991
|
+
* ```ts
|
|
4992
|
+
* const sem = Semaphore(2);
|
|
4993
|
+
* const permit = await sem.acquire();
|
|
4994
|
+
* console.log(permit.toString()); // 'SemaphorePermit'
|
|
4995
|
+
* permit.release();
|
|
4996
|
+
* console.log(permit.toString()); // 'SemaphorePermit(<released>)'
|
|
4997
|
+
* ```
|
|
4998
|
+
*/
|
|
4999
|
+
toString(): string;
|
|
5000
|
+
/**
|
|
5001
|
+
* Releases the permit back to the semaphore, allowing another waiting
|
|
5002
|
+
* operation to proceed (or incrementing the available count).
|
|
5003
|
+
*
|
|
5004
|
+
* Calling `release()` more than once is a no-op (idempotent), matching
|
|
5005
|
+
* the behavior of `MutexGuard.unlock()`.
|
|
5006
|
+
*
|
|
5007
|
+
* @example
|
|
5008
|
+
* ```ts
|
|
5009
|
+
* const sem = Semaphore(1);
|
|
5010
|
+
* const permit = await sem.acquire();
|
|
5011
|
+
* permit.release();
|
|
5012
|
+
* permit.release(); // no-op, safe to call again
|
|
5013
|
+
* ```
|
|
5014
|
+
*/
|
|
5015
|
+
release(): void;
|
|
5016
|
+
}
|
|
5017
|
+
/**
|
|
5018
|
+
* A counting semaphore for limiting async concurrency.
|
|
5019
|
+
*
|
|
5020
|
+
* Allows up to `capacity` concurrent operations. Each `acquire()` consumes
|
|
5021
|
+
* one permit; each `release()` returns one. When the limit is reached,
|
|
5022
|
+
* `acquire()` waits for a permit to be released. Waiters are served in
|
|
5023
|
+
* FIFO order.
|
|
5024
|
+
*
|
|
5025
|
+
* Unlike `Mutex<T>`, `Semaphore` does not protect a value — it is a pure
|
|
5026
|
+
* concurrency counter. For exclusive access to a value, use `Mutex<T>`.
|
|
5027
|
+
*
|
|
5028
|
+
* @since 1.10.0
|
|
5029
|
+
* @see https://docs.rs/tokio/latest/tokio/sync/struct.Semaphore.html
|
|
5030
|
+
* @example
|
|
5031
|
+
* ```ts
|
|
5032
|
+
* // Limit concurrent fetch to 10
|
|
5033
|
+
* const sem = Semaphore(10);
|
|
5034
|
+
*
|
|
5035
|
+
* async function niceFetch(url: string): Promise<Response> {
|
|
5036
|
+
* return sem.withPermit(() => fetch(url));
|
|
5037
|
+
* }
|
|
5038
|
+
*
|
|
5039
|
+
* await Promise.all(urls.map(niceFetch));
|
|
5040
|
+
* ```
|
|
5041
|
+
*
|
|
5042
|
+
* @example
|
|
5043
|
+
* ```ts
|
|
5044
|
+
* // Database connection pool with 5 connections
|
|
5045
|
+
* const pool = Semaphore(5);
|
|
5046
|
+
*
|
|
5047
|
+
* async function query(sql: string): Promise<Row[]> {
|
|
5048
|
+
* return pool.withPermit(async () => {
|
|
5049
|
+
* const conn = await getConnection();
|
|
5050
|
+
* try {
|
|
5051
|
+
* return await conn.query(sql);
|
|
5052
|
+
* } finally {
|
|
5053
|
+
* releaseConnection(conn);
|
|
5054
|
+
* }
|
|
5055
|
+
* });
|
|
5056
|
+
* }
|
|
5057
|
+
* ```
|
|
5058
|
+
*/
|
|
5059
|
+
interface Semaphore {
|
|
5060
|
+
/**
|
|
5061
|
+
* The well-known symbol `Symbol.toStringTag` used by `Object.prototype.toString()`.
|
|
5062
|
+
* Returns `'Semaphore'` so that `Object.prototype.toString.call(sem)`
|
|
5063
|
+
* produces `'[object Semaphore]'`.
|
|
5064
|
+
*/
|
|
5065
|
+
readonly [Symbol.toStringTag]: 'Semaphore';
|
|
5066
|
+
/**
|
|
5067
|
+
* Custom `toString` implementation showing available/capacity.
|
|
5068
|
+
*
|
|
5069
|
+
* @example
|
|
5070
|
+
* ```ts
|
|
5071
|
+
* const sem = Semaphore(3);
|
|
5072
|
+
* console.log(sem.toString()); // 'Semaphore(3/3)'
|
|
5073
|
+
* const permit = await sem.acquire();
|
|
5074
|
+
* console.log(sem.toString()); // 'Semaphore(2/3)'
|
|
5075
|
+
* permit.release();
|
|
5076
|
+
* console.log(sem.toString()); // 'Semaphore(3/3)'
|
|
5077
|
+
* ```
|
|
5078
|
+
*/
|
|
5079
|
+
toString(): string;
|
|
5080
|
+
/**
|
|
5081
|
+
* The maximum number of permits (concurrency limit), set at construction.
|
|
5082
|
+
*
|
|
5083
|
+
* @example
|
|
5084
|
+
* ```ts
|
|
5085
|
+
* const sem = Semaphore(5);
|
|
5086
|
+
* console.log(sem.capacity); // 5
|
|
5087
|
+
* ```
|
|
5088
|
+
*/
|
|
5089
|
+
readonly capacity: number;
|
|
5090
|
+
/**
|
|
5091
|
+
* Acquires a permit, executing the callback with at most `capacity`
|
|
5092
|
+
* concurrent callers. The permit is automatically released when the
|
|
5093
|
+
* callback settles (success or rejection).
|
|
5094
|
+
*
|
|
5095
|
+
* This is the recommended way to use the semaphore as it avoids leaking
|
|
5096
|
+
* permits on exceptions.
|
|
5097
|
+
*
|
|
5098
|
+
* @typeParam U - The return type of the callback.
|
|
5099
|
+
* @param fn - The callback to execute while holding a permit.
|
|
5100
|
+
* @returns A promise that resolves to the callback's return value.
|
|
5101
|
+
* @example
|
|
5102
|
+
* ```ts
|
|
5103
|
+
* const sem = Semaphore(3);
|
|
5104
|
+
* const result = await sem.withPermit(async () => {
|
|
5105
|
+
* return await fetch('/api/data');
|
|
5106
|
+
* });
|
|
5107
|
+
* ```
|
|
5108
|
+
*/
|
|
5109
|
+
withPermit<U>(fn: () => PromiseLike<U> | U): Promise<Awaited<U>>;
|
|
5110
|
+
/**
|
|
5111
|
+
* Acquires a permit, waiting if necessary until one is available.
|
|
5112
|
+
*
|
|
5113
|
+
* **Important:** Always release the permit in a `finally` block to avoid
|
|
5114
|
+
* leaking permits on exceptions. Prefer {@link withPermit} for automatic
|
|
5115
|
+
* release.
|
|
5116
|
+
*
|
|
5117
|
+
* @returns A promise that resolves to a {@link SemaphorePermit}.
|
|
5118
|
+
* @example
|
|
5119
|
+
* ```ts
|
|
5120
|
+
* const sem = Semaphore(2);
|
|
5121
|
+
* const permit = await sem.acquire();
|
|
5122
|
+
* try {
|
|
5123
|
+
* await doWork();
|
|
5124
|
+
* } finally {
|
|
5125
|
+
* permit.release();
|
|
5126
|
+
* }
|
|
5127
|
+
* ```
|
|
5128
|
+
*/
|
|
5129
|
+
acquire(): Promise<SemaphorePermit>;
|
|
5130
|
+
/**
|
|
5131
|
+
* Attempts to acquire a permit without waiting.
|
|
5132
|
+
*
|
|
5133
|
+
* @returns `Some(permit)` if a permit was available, `None` if the limit
|
|
5134
|
+
* has been reached.
|
|
5135
|
+
* @example
|
|
5136
|
+
* ```ts
|
|
5137
|
+
* const sem = Semaphore(1);
|
|
5138
|
+
* const maybePermit = sem.tryAcquire();
|
|
5139
|
+
* if (maybePermit.isSome()) {
|
|
5140
|
+
* const permit = maybePermit.unwrap();
|
|
5141
|
+
* try {
|
|
5142
|
+
* await doWork();
|
|
5143
|
+
* } finally {
|
|
5144
|
+
* permit.release();
|
|
5145
|
+
* }
|
|
5146
|
+
* } else {
|
|
5147
|
+
* console.log('At capacity, skipping');
|
|
5148
|
+
* }
|
|
5149
|
+
* ```
|
|
5150
|
+
*/
|
|
5151
|
+
tryAcquire(): Option<SemaphorePermit>;
|
|
5152
|
+
/**
|
|
5153
|
+
* Returns the number of permits currently available (not acquired).
|
|
5154
|
+
*
|
|
5155
|
+
* Note: this is a snapshot and may change immediately after the call as
|
|
5156
|
+
* other async operations acquire/release permits.
|
|
5157
|
+
*
|
|
5158
|
+
* @example
|
|
5159
|
+
* ```ts
|
|
5160
|
+
* const sem = Semaphore(3);
|
|
5161
|
+
* console.log(sem.availablePermits()); // 3
|
|
5162
|
+
* const permit = await sem.acquire();
|
|
5163
|
+
* console.log(sem.availablePermits()); // 2
|
|
5164
|
+
* permit.release();
|
|
5165
|
+
* console.log(sem.availablePermits()); // 3
|
|
5166
|
+
* ```
|
|
5167
|
+
*/
|
|
5168
|
+
availablePermits(): number;
|
|
5169
|
+
}
|
|
5170
|
+
/**
|
|
5171
|
+
* Creates a new `Semaphore` with the given capacity.
|
|
5172
|
+
*
|
|
5173
|
+
* @param permits - The maximum number of concurrent operations allowed.
|
|
5174
|
+
* Must be a non-negative integer. Use `0` to disallow any
|
|
5175
|
+
* concurrent acquire (acquire will wait forever).
|
|
5176
|
+
* @returns A new `Semaphore` instance.
|
|
5177
|
+
* @throws {RangeError} If `permits` is negative or not an integer.
|
|
5178
|
+
* @example
|
|
5179
|
+
* ```ts
|
|
5180
|
+
* // Limit to 5 concurrent operations
|
|
5181
|
+
* const sem = Semaphore(5);
|
|
5182
|
+
*
|
|
5183
|
+
* // Binary semaphore (equivalent to a value-less Mutex)
|
|
5184
|
+
* const binary = Semaphore(1);
|
|
5185
|
+
* ```
|
|
5186
|
+
*
|
|
5187
|
+
* @example
|
|
5188
|
+
* ```ts
|
|
5189
|
+
* // Task queue: process 2 jobs at a time
|
|
5190
|
+
* const sem = Semaphore(2);
|
|
5191
|
+
*
|
|
5192
|
+
* async function processJob(job: Job) {
|
|
5193
|
+
* return sem.withPermit(async () => {
|
|
5194
|
+
* return await runJob(job);
|
|
5195
|
+
* });
|
|
5196
|
+
* }
|
|
5197
|
+
*
|
|
5198
|
+
* await Promise.all(jobs.map(processJob));
|
|
5199
|
+
* ```
|
|
5200
|
+
*/
|
|
5201
|
+
declare function Semaphore(permits: number): Semaphore;
|
|
5202
|
+
|
|
5203
|
+
export { ASYNC_NONE, Break, Channel, Continue, Err, FnOnce, FnOnceAsync, Lazy, LazyAsync, Mutex, None, Ok, Once, OnceAsync, RESULT_FALSE, RESULT_TRUE, RESULT_VOID, RESULT_ZERO, RwLock, Semaphore, Some, isControlFlow, isOption, isResult, tryAsyncOption, tryAsyncResult, tryOption, tryResult };
|
|
5204
|
+
export type { AsyncIOResult, AsyncLikeOption, AsyncLikeResult, AsyncOption, AsyncResult, AsyncSafeResult, AsyncVoidIOResult, AsyncVoidResult, ControlFlow, IOResult, MutexGuard, Option, Receiver, Result, RwLockReadGuard, RwLockWriteGuard, SafeResult, SemaphorePermit, Sender, VoidIOResult, VoidResult };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Rust's Option, Result, and sync primitives for JavaScript/TypeScript - Better error handling and null-safety patterns.",
|
|
4
4
|
"author": "jiang115jie@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.10.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "dist/main.cjs",
|
|
9
9
|
"module": "dist/main.mjs",
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
"test": "pnpm exec vitest run --coverage",
|
|
33
33
|
"test:watch": "pnpm exec vitest",
|
|
34
34
|
"test:ui": "pnpm exec vitest --ui",
|
|
35
|
-
"predocs": "pnpm dlx rimraf docs",
|
|
36
35
|
"docs": "pnpm exec typedoc",
|
|
37
36
|
"eg": "deno run -A examples/main.ts",
|
|
38
37
|
"prepublishOnly": "pnpm run build"
|
|
@@ -54,6 +53,7 @@
|
|
|
54
53
|
"Lazy",
|
|
55
54
|
"Mutex",
|
|
56
55
|
"RwLock",
|
|
56
|
+
"Semaphore",
|
|
57
57
|
"Channel",
|
|
58
58
|
"ControlFlow",
|
|
59
59
|
"FnOnce",
|
|
@@ -65,15 +65,15 @@
|
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@eslint/js": "^10.0.1",
|
|
67
67
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
68
|
-
"@vitest/coverage-v8": "^4.1.
|
|
69
|
-
"@vitest/ui": "4.1.
|
|
70
|
-
"eslint": "^10.
|
|
71
|
-
"rollup": "^4.
|
|
68
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
69
|
+
"@vitest/ui": "4.1.9",
|
|
70
|
+
"eslint": "^10.6.0",
|
|
71
|
+
"rollup": "^4.62.2",
|
|
72
72
|
"rollup-plugin-dts": "^6.4.1",
|
|
73
73
|
"typedoc": "^0.28.19",
|
|
74
|
-
"typescript": "^6.0.
|
|
75
|
-
"typescript-eslint": "^8.
|
|
76
|
-
"vite": "^8.0
|
|
77
|
-
"vitest": "^4.1.
|
|
74
|
+
"typescript": "^6.0.3",
|
|
75
|
+
"typescript-eslint": "^8.62.0",
|
|
76
|
+
"vite": "^8.1.0",
|
|
77
|
+
"vitest": "^4.1.9"
|
|
78
78
|
}
|
|
79
79
|
}
|