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/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
- 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, Some, isControlFlow, isOption, isResult, tryAsyncOption, tryAsyncResult, tryOption, tryResult };
4907
- export type { AsyncIOResult, AsyncLikeOption, AsyncLikeResult, AsyncOption, AsyncResult, AsyncSafeResult, AsyncVoidIOResult, AsyncVoidResult, ControlFlow, IOResult, MutexGuard, Option, Receiver, Result, RwLockReadGuard, RwLockWriteGuard, SafeResult, Sender, VoidIOResult, VoidResult };
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.9.2",
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.4",
69
- "@vitest/ui": "4.1.4",
70
- "eslint": "^10.2.0",
71
- "rollup": "^4.60.1",
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.2",
75
- "typescript-eslint": "^8.58.1",
76
- "vite": "^8.0.8",
77
- "vitest": "^4.1.4"
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
  }