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 +82 -0
- package/README.cn.md +28 -24
- package/README.md +35 -26
- package/dist/{main.cjs → mod.cjs} +282 -66
- package/dist/mod.cjs.map +1 -0
- package/dist/mod.d.cts +5132 -0
- package/dist/mod.d.mts +5132 -0
- package/dist/{main.mjs → mod.mjs} +282 -67
- package/dist/mod.mjs.map +1 -0
- package/package.json +30 -30
- package/dist/main.cjs.map +0 -1
- package/dist/main.mjs.map +0 -1
- package/dist/types.d.ts +0 -4907
|
@@ -22,7 +22,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
22
22
|
*
|
|
23
23
|
* @internal
|
|
24
24
|
*/
|
|
25
|
-
const OptionKindSymbol =
|
|
25
|
+
const OptionKindSymbol = /*#__PURE__*/ Symbol("Option kind");
|
|
26
26
|
//#endregion
|
|
27
27
|
//#region src/core/option/guards.ts
|
|
28
28
|
/**
|
|
@@ -67,7 +67,7 @@ function isOption(o) {
|
|
|
67
67
|
*
|
|
68
68
|
* @internal
|
|
69
69
|
*/
|
|
70
|
-
const ResultKindSymbol =
|
|
70
|
+
const ResultKindSymbol = /*#__PURE__*/ Symbol("Result kind");
|
|
71
71
|
//#endregion
|
|
72
72
|
//#region src/core/result/guards.ts
|
|
73
73
|
/**
|
|
@@ -90,6 +90,19 @@ function isResult(r) {
|
|
|
90
90
|
return r != null && typeof r === "object" && ResultKindSymbol in r;
|
|
91
91
|
}
|
|
92
92
|
//#endregion
|
|
93
|
+
//#region src/internal/constants.ts
|
|
94
|
+
/**
|
|
95
|
+
* @module
|
|
96
|
+
* Internal cached Promise constants for async method optimization.
|
|
97
|
+
*
|
|
98
|
+
* Shared by `core/prelude.ts` and `std/sync/channel.ts` to avoid duplicate allocations.
|
|
99
|
+
* Not re-exported from `src/mod.ts`; consumers cannot import these.
|
|
100
|
+
*/
|
|
101
|
+
/** Cached `Promise<true>` for reuse by async predicate short-circuit branches. */
|
|
102
|
+
const ASYNC_TRUE = /*#__PURE__*/ Promise.resolve(true);
|
|
103
|
+
/** Cached `Promise<false>` for reuse by async predicate short-circuit branches. */
|
|
104
|
+
const ASYNC_FALSE = /*#__PURE__*/ Promise.resolve(false);
|
|
105
|
+
//#endregion
|
|
93
106
|
//#region src/core/prelude.ts
|
|
94
107
|
/**
|
|
95
108
|
* @module
|
|
@@ -102,8 +115,6 @@ function isResult(r) {
|
|
|
102
115
|
* - `Err<T, E>(error)` - Creates a failed Result
|
|
103
116
|
* - `None` interface - Type overrides for better type inference
|
|
104
117
|
*/
|
|
105
|
-
const ASYNC_TRUE$1 = /* @__PURE__ */ Promise.resolve(true);
|
|
106
|
-
const ASYNC_FALSE$1 = /* @__PURE__ */ Promise.resolve(false);
|
|
107
118
|
/**
|
|
108
119
|
* Creates an `Option<T>` representing the presence of a value.
|
|
109
120
|
* This function is typically used to construct an `Option` that contains a value, indicating that the operation yielding the value was successful.
|
|
@@ -140,13 +151,15 @@ function Some(value) {
|
|
|
140
151
|
return predicate(value);
|
|
141
152
|
},
|
|
142
153
|
isSomeAndAsync(predicate) {
|
|
143
|
-
|
|
154
|
+
const r = predicate(value);
|
|
155
|
+
return typeof r === "boolean" ? r ? ASYNC_TRUE : ASYNC_FALSE : Promise.resolve(r);
|
|
144
156
|
},
|
|
145
157
|
isNoneOr(predicate) {
|
|
146
158
|
return predicate(value);
|
|
147
159
|
},
|
|
148
160
|
isNoneOrAsync(predicate) {
|
|
149
|
-
|
|
161
|
+
const r = predicate(value);
|
|
162
|
+
return typeof r === "boolean" ? r ? ASYNC_TRUE : ASYNC_FALSE : Promise.resolve(r);
|
|
150
163
|
},
|
|
151
164
|
expect(_msg) {
|
|
152
165
|
return value;
|
|
@@ -198,9 +211,8 @@ function Some(value) {
|
|
|
198
211
|
return other.isSome() ? Some(fn(value, other.unwrap())) : None;
|
|
199
212
|
},
|
|
200
213
|
unzip() {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const [a, b] = tuple;
|
|
214
|
+
if (!Array.isArray(value) || value.length !== 2) throw new TypeError(`Option::unzip() requires a 2-element tuple, received ${Array.isArray(value) ? `array with ${value.length} elements` : typeof value}`);
|
|
215
|
+
const [a, b] = value;
|
|
204
216
|
return [Some(a), Some(b)];
|
|
205
217
|
},
|
|
206
218
|
reduce(other, fn) {
|
|
@@ -264,7 +276,7 @@ function Some(value) {
|
|
|
264
276
|
* const name = None.unwrapOr('Anonymous'); // 'Anonymous'
|
|
265
277
|
* ```
|
|
266
278
|
*/
|
|
267
|
-
const None =
|
|
279
|
+
const None = /*#__PURE__*/ Object.freeze({
|
|
268
280
|
[Symbol.toStringTag]: "Option",
|
|
269
281
|
[OptionKindSymbol]: "None",
|
|
270
282
|
*[Symbol.iterator]() {},
|
|
@@ -281,13 +293,13 @@ const None = /* @__PURE__ */ Object.freeze({
|
|
|
281
293
|
return false;
|
|
282
294
|
},
|
|
283
295
|
isSomeAndAsync(_predicate) {
|
|
284
|
-
return ASYNC_FALSE
|
|
296
|
+
return ASYNC_FALSE;
|
|
285
297
|
},
|
|
286
298
|
isNoneOr(_predicate) {
|
|
287
299
|
return true;
|
|
288
300
|
},
|
|
289
301
|
isNoneOrAsync(_predicate) {
|
|
290
|
-
return ASYNC_TRUE
|
|
302
|
+
return ASYNC_TRUE;
|
|
291
303
|
},
|
|
292
304
|
expect(msg) {
|
|
293
305
|
throw new TypeError(msg);
|
|
@@ -403,7 +415,7 @@ const None = /* @__PURE__ */ Object.freeze({
|
|
|
403
415
|
* }
|
|
404
416
|
* ```
|
|
405
417
|
*/
|
|
406
|
-
const ASYNC_NONE =
|
|
418
|
+
const ASYNC_NONE = /*#__PURE__*/ Promise.resolve(None);
|
|
407
419
|
function Ok(value) {
|
|
408
420
|
const ok = Object.freeze({
|
|
409
421
|
[Symbol.toStringTag]: "Result",
|
|
@@ -424,13 +436,14 @@ function Ok(value) {
|
|
|
424
436
|
return predicate(value);
|
|
425
437
|
},
|
|
426
438
|
isOkAndAsync(predicate) {
|
|
427
|
-
|
|
439
|
+
const r = predicate(value);
|
|
440
|
+
return typeof r === "boolean" ? r ? ASYNC_TRUE : ASYNC_FALSE : Promise.resolve(r);
|
|
428
441
|
},
|
|
429
442
|
isErrAnd(_predicate) {
|
|
430
443
|
return false;
|
|
431
444
|
},
|
|
432
445
|
isErrAndAsync(_predicate) {
|
|
433
|
-
return ASYNC_FALSE
|
|
446
|
+
return ASYNC_FALSE;
|
|
434
447
|
},
|
|
435
448
|
expect(_msg) {
|
|
436
449
|
return value;
|
|
@@ -570,13 +583,14 @@ function Err(error) {
|
|
|
570
583
|
return false;
|
|
571
584
|
},
|
|
572
585
|
isOkAndAsync(_predicate) {
|
|
573
|
-
return ASYNC_FALSE
|
|
586
|
+
return ASYNC_FALSE;
|
|
574
587
|
},
|
|
575
588
|
isErrAnd(predicate) {
|
|
576
589
|
return predicate(error);
|
|
577
590
|
},
|
|
578
591
|
isErrAndAsync(predicate) {
|
|
579
|
-
|
|
592
|
+
const r = predicate(error);
|
|
593
|
+
return typeof r === "boolean" ? r ? ASYNC_TRUE : ASYNC_FALSE : Promise.resolve(r);
|
|
580
594
|
},
|
|
581
595
|
expect(msg) {
|
|
582
596
|
throw new TypeError(`${msg}: ${error}`);
|
|
@@ -803,7 +817,7 @@ async function tryAsyncOption(task, ...args) {
|
|
|
803
817
|
* const value: boolean = RESULT_TRUE.intoOk(); // Safe extraction
|
|
804
818
|
* ```
|
|
805
819
|
*/
|
|
806
|
-
const RESULT_TRUE =
|
|
820
|
+
const RESULT_TRUE = /*#__PURE__*/ Ok(true);
|
|
807
821
|
/**
|
|
808
822
|
* Result constant for `false`.
|
|
809
823
|
* Can be used anywhere due to immutability.
|
|
@@ -818,7 +832,7 @@ const RESULT_TRUE = /* @__PURE__ */ Ok(true);
|
|
|
818
832
|
* const value: boolean = RESULT_FALSE.intoOk(); // Safe extraction
|
|
819
833
|
* ```
|
|
820
834
|
*/
|
|
821
|
-
const RESULT_FALSE =
|
|
835
|
+
const RESULT_FALSE = /*#__PURE__*/ Ok(false);
|
|
822
836
|
/**
|
|
823
837
|
* Result constant for `0`.
|
|
824
838
|
* Can be used anywhere due to immutability.
|
|
@@ -833,7 +847,7 @@ const RESULT_FALSE = /* @__PURE__ */ Ok(false);
|
|
|
833
847
|
* const value: number = RESULT_ZERO.intoOk(); // Safe extraction
|
|
834
848
|
* ```
|
|
835
849
|
*/
|
|
836
|
-
const RESULT_ZERO =
|
|
850
|
+
const RESULT_ZERO = /*#__PURE__*/ Ok(0);
|
|
837
851
|
/**
|
|
838
852
|
* Result constant for `void` or `()`.
|
|
839
853
|
* Can be used anywhere due to immutability.
|
|
@@ -848,7 +862,7 @@ const RESULT_ZERO = /* @__PURE__ */ Ok(0);
|
|
|
848
862
|
* RESULT_VOID.intoOk(); // Safe extraction (returns undefined)
|
|
849
863
|
* ```
|
|
850
864
|
*/
|
|
851
|
-
const RESULT_VOID =
|
|
865
|
+
const RESULT_VOID = /*#__PURE__*/ Ok();
|
|
852
866
|
//#endregion
|
|
853
867
|
//#region src/core/result/extensions.ts
|
|
854
868
|
/**
|
|
@@ -935,7 +949,7 @@ async function tryAsyncResult(task, ...args) {
|
|
|
935
949
|
*
|
|
936
950
|
* @internal
|
|
937
951
|
*/
|
|
938
|
-
const ControlFlowKindSymbol =
|
|
952
|
+
const ControlFlowKindSymbol = /*#__PURE__*/ Symbol("ControlFlow kind");
|
|
939
953
|
//#endregion
|
|
940
954
|
//#region src/std/ops/control_flow.ts
|
|
941
955
|
/**
|
|
@@ -1148,8 +1162,6 @@ function isControlFlow(cf) {
|
|
|
1148
1162
|
* Supports rendezvous (capacity=0) for synchronous handoff between sender and receiver.
|
|
1149
1163
|
*
|
|
1150
1164
|
*/
|
|
1151
|
-
const ASYNC_TRUE = /* @__PURE__ */ Promise.resolve(true);
|
|
1152
|
-
const ASYNC_FALSE = /* @__PURE__ */ Promise.resolve(false);
|
|
1153
1165
|
/**
|
|
1154
1166
|
* Creates a new MPMC channel with the specified capacity.
|
|
1155
1167
|
*
|
|
@@ -1195,7 +1207,7 @@ const ASYNC_FALSE = /* @__PURE__ */ Promise.resolve(false);
|
|
|
1195
1207
|
*
|
|
1196
1208
|
* // Multiple producers
|
|
1197
1209
|
* async function logFromService(name: string) {
|
|
1198
|
-
* const sender = logs.sender
|
|
1210
|
+
* const sender = logs.sender;
|
|
1199
1211
|
* await sender.send(`[${name}] Started`);
|
|
1200
1212
|
* // ... do work ...
|
|
1201
1213
|
* await sender.send(`[${name}] Finished`);
|
|
@@ -1203,7 +1215,7 @@ const ASYNC_FALSE = /* @__PURE__ */ Promise.resolve(false);
|
|
|
1203
1215
|
*
|
|
1204
1216
|
* // Single consumer writing to file
|
|
1205
1217
|
* async function writeLogsToFile() {
|
|
1206
|
-
* const receiver = logs.receiver
|
|
1218
|
+
* const receiver = logs.receiver;
|
|
1207
1219
|
* for await (const log of receiver) {
|
|
1208
1220
|
* await fs.appendFile('app.log', log + '\n');
|
|
1209
1221
|
* }
|
|
@@ -1282,7 +1294,7 @@ function Channel(capacity = Infinity) {
|
|
|
1282
1294
|
sendWaitQueue.push(waiter);
|
|
1283
1295
|
const timeoutId = setTimeout(() => {
|
|
1284
1296
|
const index = sendWaitQueue.indexOf(waiter);
|
|
1285
|
-
sendWaitQueue.splice(index, 1);
|
|
1297
|
+
if (index >= 0) sendWaitQueue.splice(index, 1);
|
|
1286
1298
|
resolve(false);
|
|
1287
1299
|
}, ms);
|
|
1288
1300
|
const originalResolve = waiter.resolve;
|
|
@@ -1304,23 +1316,61 @@ function Channel(capacity = Infinity) {
|
|
|
1304
1316
|
receiveWaitQueue.push(wrappedWaiter);
|
|
1305
1317
|
const timeoutId = setTimeout(() => {
|
|
1306
1318
|
const index = receiveWaitQueue.indexOf(wrappedWaiter);
|
|
1307
|
-
receiveWaitQueue.splice(index, 1);
|
|
1319
|
+
if (index >= 0) receiveWaitQueue.splice(index, 1);
|
|
1308
1320
|
resolve(None);
|
|
1309
1321
|
}, ms);
|
|
1310
1322
|
});
|
|
1311
1323
|
}
|
|
1312
1324
|
function asyncIterator() {
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1325
|
+
let finished = false;
|
|
1326
|
+
const parked = /* @__PURE__ */ new Set();
|
|
1327
|
+
return {
|
|
1328
|
+
[Symbol.asyncIterator]() {
|
|
1329
|
+
return this;
|
|
1330
|
+
},
|
|
1331
|
+
next() {
|
|
1332
|
+
if (finished) return Promise.resolve({
|
|
1333
|
+
done: true,
|
|
1334
|
+
value: void 0
|
|
1335
|
+
});
|
|
1336
|
+
const result = tryReceive();
|
|
1337
|
+
if (result.isSome()) return Promise.resolve({
|
|
1338
|
+
done: false,
|
|
1339
|
+
value: result.unwrap()
|
|
1340
|
+
});
|
|
1341
|
+
if (closed) return Promise.resolve({
|
|
1342
|
+
done: true,
|
|
1343
|
+
value: void 0
|
|
1344
|
+
});
|
|
1345
|
+
return new Promise((resolve) => {
|
|
1346
|
+
const waiter = (value) => {
|
|
1347
|
+
parked.delete(waiter);
|
|
1348
|
+
if (value.isNone()) resolve({
|
|
1349
|
+
done: true,
|
|
1350
|
+
value: void 0
|
|
1351
|
+
});
|
|
1352
|
+
else resolve({
|
|
1353
|
+
done: false,
|
|
1354
|
+
value: value.unwrap()
|
|
1355
|
+
});
|
|
1356
|
+
};
|
|
1357
|
+
parked.add(waiter);
|
|
1358
|
+
receiveWaitQueue.push(waiter);
|
|
1359
|
+
});
|
|
1360
|
+
},
|
|
1361
|
+
return() {
|
|
1362
|
+
finished = true;
|
|
1363
|
+
for (const waiter of parked) {
|
|
1364
|
+
receiveWaitQueue.splice(receiveWaitQueue.indexOf(waiter), 1);
|
|
1365
|
+
waiter(None);
|
|
1366
|
+
}
|
|
1367
|
+
parked.clear();
|
|
1368
|
+
return Promise.resolve({
|
|
1369
|
+
done: true,
|
|
1370
|
+
value: void 0
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1324
1374
|
}
|
|
1325
1375
|
function createSender() {
|
|
1326
1376
|
return Object.freeze({
|
|
@@ -1983,21 +2033,73 @@ function OnceAsync() {
|
|
|
1983
2033
|
for (const waiter of waiters) waiter(val);
|
|
1984
2034
|
waiters = [];
|
|
1985
2035
|
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Claims the in-flight slot for a fresh initialization attempt.
|
|
2038
|
+
*
|
|
2039
|
+
* The joinable `pendingPromise` is created *before* the caller invokes its
|
|
2040
|
+
* initializer, so a synchronously-executing initializer that re-enters
|
|
2041
|
+
* through `getOrInit`/`getOrTryInit` (or calls `set`/`tryInsert`) sees the
|
|
2042
|
+
* slot as taken and joins/waits instead of starting a second attempt.
|
|
2043
|
+
*
|
|
2044
|
+
* The marker is cleared as soon as the attempt settles, and this handler is
|
|
2045
|
+
* registered first so it runs before any waiter's retry handler — a retry
|
|
2046
|
+
* must observe the cell as free to start a fresh attempt.
|
|
2047
|
+
*/
|
|
2048
|
+
function claimAttempt() {
|
|
2049
|
+
let commit;
|
|
2050
|
+
let fail;
|
|
2051
|
+
const attempt = new Promise((resolve, reject) => {
|
|
2052
|
+
commit = (val) => resolve(val);
|
|
2053
|
+
fail = (reason) => reject(reason);
|
|
2054
|
+
});
|
|
2055
|
+
pendingPromise = attempt;
|
|
2056
|
+
const clear = () => {
|
|
2057
|
+
if (pendingPromise === attempt) pendingPromise = void 0;
|
|
2058
|
+
};
|
|
2059
|
+
attempt.then(clear, clear);
|
|
2060
|
+
return {
|
|
2061
|
+
attempt,
|
|
2062
|
+
commit,
|
|
2063
|
+
fail,
|
|
2064
|
+
clear
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
1986
2067
|
function getOrTryInit(fn) {
|
|
1987
2068
|
if (initialized) return resolvedResultPromise ??= Promise.resolve(Ok(value));
|
|
1988
2069
|
if (pendingPromise) return pendingPromise.then(() => Ok(value), () => getOrTryInit(fn));
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2070
|
+
const { attempt, commit, fail, clear } = claimAttempt();
|
|
2071
|
+
let resultPromise;
|
|
2072
|
+
try {
|
|
2073
|
+
resultPromise = Promise.resolve(fn());
|
|
2074
|
+
resultPromise.then((result) => {
|
|
2075
|
+
if (result.isOk()) {
|
|
2076
|
+
const val = result.unwrap();
|
|
2077
|
+
setValue(val);
|
|
2078
|
+
commit(val);
|
|
2079
|
+
} else fail(result);
|
|
2080
|
+
}, fail).catch(fail);
|
|
2081
|
+
} catch (error) {
|
|
2082
|
+
fail(error);
|
|
2083
|
+
clear();
|
|
2084
|
+
throw error;
|
|
2085
|
+
}
|
|
2086
|
+
return attempt.then(() => resultPromise, () => resultPromise);
|
|
2087
|
+
}
|
|
2088
|
+
function getOrInit(fn) {
|
|
2089
|
+
if (initialized) return resolvedPromise ??= Promise.resolve(value);
|
|
2090
|
+
if (pendingPromise) return pendingPromise.then(() => resolvedPromise ??= Promise.resolve(value), () => getOrInit(fn));
|
|
2091
|
+
const { attempt, commit, fail, clear } = claimAttempt();
|
|
2092
|
+
try {
|
|
2093
|
+
Promise.resolve(fn()).then((result) => {
|
|
2094
|
+
setValue(result);
|
|
2095
|
+
commit(result);
|
|
2096
|
+
}, fail).catch(fail);
|
|
2097
|
+
} catch (error) {
|
|
2098
|
+
fail(error);
|
|
2099
|
+
clear();
|
|
2100
|
+
throw error;
|
|
2101
|
+
}
|
|
2102
|
+
return attempt;
|
|
2001
2103
|
}
|
|
2002
2104
|
return Object.freeze({
|
|
2003
2105
|
[Symbol.toStringTag]: "OnceAsync",
|
|
@@ -2008,26 +2110,16 @@ function OnceAsync() {
|
|
|
2008
2110
|
return initialized ? Some(value) : None;
|
|
2009
2111
|
},
|
|
2010
2112
|
set(newValue) {
|
|
2011
|
-
if (initialized) return Err(newValue);
|
|
2113
|
+
if (initialized || pendingPromise !== void 0) return Err(newValue);
|
|
2012
2114
|
setValue(newValue);
|
|
2013
2115
|
return RESULT_VOID;
|
|
2014
2116
|
},
|
|
2015
2117
|
tryInsert(newValue) {
|
|
2016
|
-
if (initialized) return Err([value, newValue]);
|
|
2118
|
+
if (initialized || pendingPromise !== void 0) return Err([initialized ? value : void 0, newValue]);
|
|
2017
2119
|
setValue(newValue);
|
|
2018
2120
|
return Ok(newValue);
|
|
2019
2121
|
},
|
|
2020
|
-
getOrInit
|
|
2021
|
-
if (initialized) return resolvedPromise ??= Promise.resolve(value);
|
|
2022
|
-
if (pendingPromise) return pendingPromise;
|
|
2023
|
-
pendingPromise = Promise.resolve(fn()).then((result) => {
|
|
2024
|
-
setValue(result);
|
|
2025
|
-
return result;
|
|
2026
|
-
}).finally(() => {
|
|
2027
|
-
pendingPromise = void 0;
|
|
2028
|
-
});
|
|
2029
|
-
return pendingPromise;
|
|
2030
|
-
},
|
|
2122
|
+
getOrInit,
|
|
2031
2123
|
getOrTryInit,
|
|
2032
2124
|
take() {
|
|
2033
2125
|
if (!initialized) return None;
|
|
@@ -2043,7 +2135,6 @@ function OnceAsync() {
|
|
|
2043
2135
|
},
|
|
2044
2136
|
wait() {
|
|
2045
2137
|
if (initialized) return resolvedPromise ??= Promise.resolve(value);
|
|
2046
|
-
if (pendingPromise) return pendingPromise;
|
|
2047
2138
|
return new Promise((resolve) => {
|
|
2048
2139
|
waiters.push(resolve);
|
|
2049
2140
|
});
|
|
@@ -2215,6 +2306,17 @@ function RwLock(value) {
|
|
|
2215
2306
|
if (released) return;
|
|
2216
2307
|
released = true;
|
|
2217
2308
|
releaseWrite();
|
|
2309
|
+
},
|
|
2310
|
+
downgrade() {
|
|
2311
|
+
if (released) throw new Error("RwLockWriteGuard has been released");
|
|
2312
|
+
released = true;
|
|
2313
|
+
writer = false;
|
|
2314
|
+
readers = 1;
|
|
2315
|
+
while (readWaitQueue.length > 0) {
|
|
2316
|
+
readers++;
|
|
2317
|
+
readWaitQueue.shift()();
|
|
2318
|
+
}
|
|
2319
|
+
return createReadGuard();
|
|
2218
2320
|
}
|
|
2219
2321
|
});
|
|
2220
2322
|
}
|
|
@@ -2303,6 +2405,119 @@ function RwLock(value) {
|
|
|
2303
2405
|
});
|
|
2304
2406
|
}
|
|
2305
2407
|
//#endregion
|
|
2408
|
+
//#region src/std/sync/semaphore.ts
|
|
2409
|
+
/**
|
|
2410
|
+
* @module
|
|
2411
|
+
* Counting semaphore for limiting async concurrency.
|
|
2412
|
+
*
|
|
2413
|
+
* Inspired by [tokio's `Semaphore`](https://docs.rs/tokio/latest/tokio/sync/struct.Semaphore.html)
|
|
2414
|
+
* (Rust std does not include one). Unlike `Mutex<T>` which binds to a value,
|
|
2415
|
+
* `Semaphore` is a pure concurrency counter: it limits how many async
|
|
2416
|
+
* operations can run concurrently without protecting any data.
|
|
2417
|
+
*
|
|
2418
|
+
* **When to use `Semaphore` vs `Mutex<T>`:**
|
|
2419
|
+
* - Use `Mutex<T>` for exclusive access to a value (n=1, with data)
|
|
2420
|
+
* - Use `Semaphore` to limit concurrency to N (e.g. fetch rate limiting,
|
|
2421
|
+
* connection pools, task queues)
|
|
2422
|
+
*
|
|
2423
|
+
* `Semaphore(1)` behaves like a `Mutex` without a value (a binary semaphore),
|
|
2424
|
+
* but `Mutex<T>` is preferred when you need to protect a value since the
|
|
2425
|
+
* guard provides typed access via `value`.
|
|
2426
|
+
*/
|
|
2427
|
+
/**
|
|
2428
|
+
* Creates a new `Semaphore` with the given capacity.
|
|
2429
|
+
*
|
|
2430
|
+
* @param permits - The maximum number of concurrent operations allowed.
|
|
2431
|
+
* Must be a non-negative integer. Use `0` to disallow any
|
|
2432
|
+
* concurrent acquire (acquire will wait forever).
|
|
2433
|
+
* @returns A new `Semaphore` instance.
|
|
2434
|
+
* @throws {RangeError} If `permits` is negative or not an integer.
|
|
2435
|
+
* @example
|
|
2436
|
+
* ```ts
|
|
2437
|
+
* // Limit to 5 concurrent operations
|
|
2438
|
+
* const sem = Semaphore(5);
|
|
2439
|
+
*
|
|
2440
|
+
* // Binary semaphore (equivalent to a value-less Mutex)
|
|
2441
|
+
* const binary = Semaphore(1);
|
|
2442
|
+
* ```
|
|
2443
|
+
*
|
|
2444
|
+
* @example
|
|
2445
|
+
* ```ts
|
|
2446
|
+
* // Task queue: process 2 jobs at a time
|
|
2447
|
+
* const sem = Semaphore(2);
|
|
2448
|
+
*
|
|
2449
|
+
* async function processJob(job: Job) {
|
|
2450
|
+
* return sem.withPermit(async () => {
|
|
2451
|
+
* return await runJob(job);
|
|
2452
|
+
* });
|
|
2453
|
+
* }
|
|
2454
|
+
*
|
|
2455
|
+
* await Promise.all(jobs.map(processJob));
|
|
2456
|
+
* ```
|
|
2457
|
+
*/
|
|
2458
|
+
function Semaphore(permits) {
|
|
2459
|
+
if (!Number.isInteger(permits) || permits < 0) throw new RangeError(`Semaphore capacity must be a non-negative integer, got ${permits}`);
|
|
2460
|
+
const capacity = permits;
|
|
2461
|
+
let available = permits;
|
|
2462
|
+
const waitQueue = [];
|
|
2463
|
+
function releasePermit() {
|
|
2464
|
+
if (waitQueue.length > 0) waitQueue.shift()();
|
|
2465
|
+
else available++;
|
|
2466
|
+
}
|
|
2467
|
+
function createPermit() {
|
|
2468
|
+
let released = false;
|
|
2469
|
+
return Object.freeze({
|
|
2470
|
+
[Symbol.toStringTag]: "SemaphorePermit",
|
|
2471
|
+
toString() {
|
|
2472
|
+
return released ? "SemaphorePermit(<released>)" : "SemaphorePermit";
|
|
2473
|
+
},
|
|
2474
|
+
release() {
|
|
2475
|
+
if (released) return;
|
|
2476
|
+
released = true;
|
|
2477
|
+
releasePermit();
|
|
2478
|
+
}
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
function acquire() {
|
|
2482
|
+
if (available > 0) {
|
|
2483
|
+
available--;
|
|
2484
|
+
return Promise.resolve(createPermit());
|
|
2485
|
+
}
|
|
2486
|
+
return new Promise((resolve) => {
|
|
2487
|
+
waitQueue.push(() => {
|
|
2488
|
+
resolve(createPermit());
|
|
2489
|
+
});
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
function tryAcquire() {
|
|
2493
|
+
if (available > 0) {
|
|
2494
|
+
available--;
|
|
2495
|
+
return Some(createPermit());
|
|
2496
|
+
}
|
|
2497
|
+
return None;
|
|
2498
|
+
}
|
|
2499
|
+
return Object.freeze({
|
|
2500
|
+
[Symbol.toStringTag]: "Semaphore",
|
|
2501
|
+
toString() {
|
|
2502
|
+
return `Semaphore(${available}/${capacity})`;
|
|
2503
|
+
},
|
|
2504
|
+
capacity,
|
|
2505
|
+
async withPermit(fn) {
|
|
2506
|
+
const permit = await acquire();
|
|
2507
|
+
try {
|
|
2508
|
+
return await fn();
|
|
2509
|
+
} finally {
|
|
2510
|
+
permit.release();
|
|
2511
|
+
}
|
|
2512
|
+
},
|
|
2513
|
+
acquire,
|
|
2514
|
+
tryAcquire,
|
|
2515
|
+
availablePermits() {
|
|
2516
|
+
return available;
|
|
2517
|
+
}
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
//#endregion
|
|
2306
2521
|
exports.ASYNC_NONE = ASYNC_NONE;
|
|
2307
2522
|
exports.Break = Break;
|
|
2308
2523
|
exports.Channel = Channel;
|
|
@@ -2322,6 +2537,7 @@ exports.RESULT_TRUE = RESULT_TRUE;
|
|
|
2322
2537
|
exports.RESULT_VOID = RESULT_VOID;
|
|
2323
2538
|
exports.RESULT_ZERO = RESULT_ZERO;
|
|
2324
2539
|
exports.RwLock = RwLock;
|
|
2540
|
+
exports.Semaphore = Semaphore;
|
|
2325
2541
|
exports.Some = Some;
|
|
2326
2542
|
exports.isControlFlow = isControlFlow;
|
|
2327
2543
|
exports.isOption = isOption;
|
|
@@ -2331,4 +2547,4 @@ exports.tryAsyncResult = tryAsyncResult;
|
|
|
2331
2547
|
exports.tryOption = tryOption;
|
|
2332
2548
|
exports.tryResult = tryResult;
|
|
2333
2549
|
|
|
2334
|
-
//# sourceMappingURL=
|
|
2550
|
+
//# sourceMappingURL=mod.cjs.map
|