UNPKG

rsxjs

Version:

Resilience Extensions for JS.

1,373 lines (1,327 loc) 35.6 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); var __export = (target, all) => { __markAsModule(target); for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __reExport = (target, module2, desc) => { if (module2 && typeof module2 === "object" || typeof module2 === "function") { for (let key of __getOwnPropNames(module2)) if (!__hasOwnProp.call(target, key) && key !== "default") __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable }); } return target; }; var __toModule = (module2) => { return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2); }; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; // src/index.ts __export(exports, { Breaker: () => breaker_exports, Deferral: () => deferral_exports, Fallback: () => fallback_exports, MemoryStore: () => MemoryStore, Mutex: () => mutex_exports, Pool: () => pool_exports, RedisStore: () => RedisStore, Semaphore: () => semaphore_exports, Timeout: () => timeout_exports, WaitGroup: () => WaitGroup, co: () => co, makeChan: () => makeChan, select: () => select }); // src/breaker/index.ts var breaker_exports = {}; __export(breaker_exports, { fromAsync: () => fromAsync, fromCallback: () => fromCallback, fromSync: () => fromSync }); // src/breaker/types.ts var import_uuid = __toModule(require("uuid")); var import_debug4 = __toModule(require("debug")); // src/channel/channel.ts var import_debug2 = __toModule(require("debug")); var import_check_types = __toModule(require("check-types")); // src/types.ts function defer() { const d = {}; d.promise = new Promise((resolve, reject) => { d.resolve = resolve; d.reject = reject; }); return d; } var Referred = class { _ref; ref() { this._ref = setInterval(() => { }, 2 ** 31 - 1); } unref() { if (this._ref) clearTimeout(this._ref); } }; var Lock = class extends Referred { requests = []; }; // src/utils.ts var utils_exports = {}; __export(utils_exports, { any: () => any, debounce: () => debounce, delay: () => delay, isDefined: () => isDefined }); var import_debug = __toModule(require("debug")); var debug = (0, import_debug.default)("rsxjs:utils"); function isDefined(v) { return v !== void 0 && v !== null; } function debounce(fn, timeout) { const state = {}; return function debouncedFn(...args) { const since = Date.now() - (state.lastCall || 0); debug(`debounced function called =>`, { since, timeout }); if (!state.lastCall || since < timeout) { state.lastCall = Date.now(); if (state.timer) { clearTimeout(state.timer); } state.timer = setTimeout(() => debouncedFn.apply(this, args), timeout + 1); return; } return fn.apply(this, args); }; } function delay(timeout) { return new Promise((resolve) => setTimeout(() => resolve(), timeout)); } function any(p) { const d = defer(); for (let i = 0; i < p.length; ++i) { p[i].then((v) => d.resolve([i, v]), d.reject); } return d.promise; } // src/channel/channel.ts var debug2 = (0, import_debug2.default)("rsxjs:channel"); var _Channel = class { constructor(config = { bufferSize: 0 }) { this.config = config; _Channel.chanMap.set(this.selectSymbol, this); import_check_types.default.assert.object(config); import_check_types.default.assert.maybe.number(config.bufferSize); } isOpen = true; buffer = []; putters = []; takers = []; selectSymbol = Symbol(); [Symbol.toPrimitive]() { return this.selectSymbol; } static getChannel(sym) { const chan5 = _Channel.chanMap.get(sym); if (!chan5) { throw new Error(`Failed to fetch closed channel`); } return chan5; } close() { this.isOpen = false; _Channel.chanMap.delete(this.selectSymbol); } async _put(dir, value, timeout) { if (!this.isOpen) { debug2(`refusing to put on a closed channel`); return { ok: false }; } const nextTaker = dir === "left" ? this.takers.pop() : this.takers.shift(); if (nextTaker) { debug2(`found pending taker, forwarding value`); nextTaker.resolve(value); return { ok: true }; } if (this.buffer.length === this.config.bufferSize) { const signal = defer(); const putter = { signal, value }; this.putters.push(putter); debug2(`added background putter, waiting for signal`); if (isDefined(timeout)) { const [i] = await any([signal.promise, delay(timeout)]); if (i === 0) { debug2(`background signal received for putter, value was read`); } else { debug2(`background putter timed out`); this.putters = this.putters.filter((putterInList) => { return putterInList !== putter; }); } return { ok: i === 0 }; } await signal.promise; return { ok: true }; } if (dir === "right") { debug2(`putter wrote value to the right of the buffer`); this.buffer.push(value); } else { debug2(`putter wrote value to the left of the buffer`); this.buffer.unshift(value); } return { ok: true }; } put(value, timeout) { return this._put("right", value, timeout); } lput(value, timeout) { return this._put("left", value, timeout); } async _take(dir, timeout) { const { value, ok } = dir === "left" ? this.select() : this.rselect(); if (ok) { return { value, ok }; } if (!this.isOpen) { debug2(`channel is closed & empty, refusing to take`); return { ok: false }; } if (this.buffer.length === 0) { const nextPutter = dir === "left" ? this.putters.shift() : this.putters.pop(); if (nextPutter) { nextPutter.signal.resolve(); debug2(`taker found an empty buffer & a background putter, stealing value`); return { ok: true, value: nextPutter.value }; } const p = defer(); this.takers.push(p); debug2(`adding taker to background list`); if (isDefined(timeout)) { const [i, value3] = await any([p.promise, delay(timeout)]); if (i === 0) { debug2(`received value from background taker`); return { ok: true, value: value3 }; } debug2(`failed to received value from background taker, timed out`); this.takers = this.takers.filter((takerFromList) => { return takerFromList !== p; }); return { ok: false }; } const value2 = await p.promise; debug2(`received value from background taker`); return { ok: true, value: value2 }; } const poppedValue = dir === "left" ? this.buffer.shift() : this.buffer.pop(); debug2(`read value from static buffer`); return { ok: !!poppedValue, value: poppedValue }; } take(timeout) { return this._take("left", timeout); } rtake(timeout) { return this._take("right", timeout); } _select(dir) { if (this.buffer.length > 0) { debug2(`selected value from %s of buffer`, dir); return { value: dir === "left" ? this.buffer.shift() : this.buffer.pop(), ok: true }; } const nextPutter = dir === "left" ? this.putters.shift() : this.putters.pop(); if (nextPutter) { debug2(`selected value from background putter`); nextPutter.signal.resolve(); return { value: nextPutter.value, ok: true }; } debug2(`failed to select a value`); return { ok: false }; } select() { return this._select("left"); } rselect() { return this._select("right"); } async *[Symbol.asyncIterator]() { while (this.isOpen) { const { value } = await this.take(); yield value; } while (true) { const { value, ok } = this.select(); if (ok) { yield value; } else { break; } } } readOnlyChan; readOnly() { return this.readOnlyChan = this.readOnlyChan || new ReadOnlyChannel(this); } writeOnlyChan; writeOnly() { return this.writeOnlyChan = this.writeOnlyChan || new WriteOnlyChannel(this); } }; var Channel = _Channel; __publicField(Channel, "chanMap", new Map()); var ReadOnlyChannel = class { constructor(chan5) { this.chan = chan5; } take() { return this.chan.take(); } select() { return this.chan.select(); } [Symbol.asyncIterator]() { return this.chan[Symbol.asyncIterator](); } [Symbol.toPrimitive]() { return this.chan[Symbol.toPrimitive](); } }; var WriteOnlyChannel = class { constructor(chan5) { this.chan = chan5; } put(value, timeout) { return this.chan.put(value, timeout); } }; function makeChan(opts) { return new Channel(opts); } // src/channel/select.ts function select(cases) { const defaultCase = cases._; const syms = Object.getOwnPropertySymbols(cases); if (syms.length === 0) { throw new Error("No channels given to select"); } const chans = []; for (const sym of syms) { chans.push(Channel.getChannel(sym)); } function poll() { for (const chan5 of chans) { const { value, ok } = chan5.select(); if (ok) { const sym = chan5[Symbol.toPrimitive](); return cases[sym](value); } } if (defaultCase) { return defaultCase(); } return new Promise((resolve) => setTimeout(() => resolve(poll()), 0)); } return poll(); } // src/store/memory.ts var map; var MemoryStore = class { map = map = map || new Map(); async incr(key) { this.map.set(key, (this.map.get(key) | 0) + 1); } async decr(key) { this.map.set(key, (this.map.get(key) | 0) - 1); } async get(key) { return this.map.get(key); } async set(key, value, options) { if (options) { if (options.notExists && this.map.has(key)) { return; } if (options.expires !== void 0) { setTimeout(() => this.map.delete(key), options.expires); } } this.map.set(key, value); } async del(key) { this.map.delete(key); } async hget(namespace, key, defaultValue) { const map2 = this.map.get(namespace); if (!map2) { return defaultValue; } return map2.get(key) || defaultValue; } async hset(namespace, key, value) { const map2 = this.map.get(namespace) || new Map(); this.map.set(namespace, map2); map2.set(key, value); } async hincr(namespace, key) { return this.hset(namespace, key, 1 + await this.hget(namespace, key, 0)); } async hdecr(namespace, key) { return this.hset(namespace, key, -1 + await this.hget(namespace, key, 0)); } async rpush(listName, value) { if (!this.map.has(listName)) { this.map.set(listName, makeChan({ bufferSize: 1e9 })); } const q = this.map.get(listName); await q.put(value); return this.map.size; } async lpush(listName, value) { if (!this.map.has(listName)) { this.map.set(listName, makeChan({ bufferSize: 1e9 })); } const q = this.map.get(listName); await q.lput(value); return this.map.size; } async rpop(listName) { const q = this.map.get(listName); if (q) { return q.rselect().value; } } async lpop(listName) { const q = this.map.get(listName); if (q) { return q.select().value; } } async brpop(listName, timeout) { const q = this.map.get(listName); if (q) { return (await q.rtake(timeout)).value; } } async blpop(listName, timeout) { const q = this.map.get(listName); if (q) { return (await q.take(timeout)).value; } } }; // src/store/redis.ts var import_debug3 = __toModule(require("debug")); var debug3 = (0, import_debug3.default)("rsxjs"); function parse(text) { if (text) { return JSON.parse(text); } } var RedisStore = class { redis; constructor(options) { this.redis = new (require("ioredis"))(options); } async get(key) { return parse(await this.redis.get(key)); } async set(key, value, options) { if (options) { if (options.notExists && options.expires) { await this.redis.set(key, JSON.stringify(value), "NX", "EX", Math.floor(options.expires / 1e3)); return; } if (options.notExists) { await this.redis.set(key, JSON.stringify(value), "NX"); return; } if (options.expires) { await this.redis.set(key, JSON.stringify(value), "EX", Math.floor(options.expires / 1e3)); return; } } await this.redis.set(key, JSON.stringify(value)); } async incr(key) { await this.redis.incr(key); } async decr(key) { await this.redis.decr(key); } async hincr(namespace, key) { await this.redis.hincrby(namespace, key, 1); } async hdecr(namespace, key) { await this.redis.hincrby(namespace, key, -1); } async del(key) { await this.redis.del(key); } async hset(namespace, key, value) { await this.redis.hset(namespace, key, JSON.stringify(value)); } async hget(namespace, key, defaultValue) { try { return parse(await this.redis.hget(namespace, key)) || defaultValue; } catch (err) { debug3(`failed to hget ${namespace} ${key} => ${err}`); return defaultValue; } } rpush(listName, value) { return this.redis.rpush(listName, JSON.parse(value)); } lpush(listName, value) { return this.redis.lpush(listName, JSON.parse(value)); } async rpop(listName) { return JSON.parse(await this.redis.rpop(listName)); } async lpop(listName) { return JSON.parse(await this.redis.lpop(listName)); } async brpop(listName, timeout) { const [_, data] = await this.redis.brpop(listName, String(timeout)); return JSON.parse(data); } async blpop(listName, timeout) { const [_, data] = await this.redis.blpop(listName, String(timeout)); return JSON.parse(data); } }; // src/store/state.ts var State = class { store; namespace; defaults; constructor({ store, namespace, defaults: defaults5 }) { if (!store) { throw new Error(`Store is required to create a state`); } if (!namespace) { throw new Error(`Namespace is required to create a new state`); } if (!defaults5) { throw new Error(`Defaults are required to create a new state`); } this.store = store; this.namespace = namespace; this.defaults = defaults5; } get(key) { return this.store.hget(this.namespace, key, this.defaults[key]); } set(key, value) { return this.store.hset(this.namespace, key, value); } incr(key) { return this.store.hincr(this.namespace, key); } decr(key) { return this.store.hdecr(this.namespace, key); } reset() { return this.store.del(this.namespace); } async dump() { const state = {}; const keys = Object.keys(this.defaults); for (const key of keys) { state[key] = await this.get(key); } return state; } }; // src/breaker/types.ts var debug4 = (0, import_debug4.default)("rsxjs"); function defaults(config) { const updatedConfig = Object.assign({}, DefaultBreakerOptions); if (!config) { return updatedConfig; } if (config.maxErrors !== void 0) { const { maxErrors } = config; if (typeof maxErrors !== "number" || maxErrors < -1 || !isFinite(maxErrors) || isNaN(maxErrors)) { throw new TypeError(`Unexpected value of type ${typeof maxErrors} given to 'maxErrors'`); } updatedConfig.maxErrors = maxErrors; } if (config.timeout !== void 0) { const { timeout } = config; if (typeof timeout !== "number" || timeout < -1 || !isFinite(timeout) || isNaN(timeout)) { throw new TypeError(`Unexpected value of type ${typeof timeout} given to 'timeout'`); } updatedConfig.timeout = timeout; } if (config.store) { updatedConfig.store = config.store; } if (config.name) { updatedConfig.name = config.name; } return updatedConfig; } var DefaultBreakerOptions = { maxErrors: 10, timeout: 10 * 1e3, store: new MemoryStore() }; var BreakerState; (function(BreakerState2) { BreakerState2[BreakerState2["OPEN"] = 0] = "OPEN"; BreakerState2[BreakerState2["CLOSED"] = 1] = "CLOSED"; BreakerState2[BreakerState2["HALFOPEN"] = 2] = "HALFOPEN"; })(BreakerState || (BreakerState = {})); var CircuitBreaker = class { constructor(options) { this.options = options; this.namespace = `rsxjs:breaker:${options.name || (0, import_uuid.v4)()}`; this.state = new State({ store: options.store, namespace: this.namespace, defaults: { numErrors: 0, lastError: "Unknown error", lastErrorTime: 0 } }); } namespace; state; lastState; lastStateTime; lastStateIsFresh() { return this.lastState !== void 0 && this.lastStateTime !== void 0 && Date.now() - this.lastStateTime < this.options.timeout; } async getFreshState() { const numErrors = await this.state.get("numErrors"); const lastErrorTime = await this.state.get("lastErrorTime"); if (debug4.enabled) { debug4(`breaker(${this.namespace}) =>`, { numErrors, lastErrorTime }); debug4(`breaker(${this.namespace}) => state dump`, await this.state.dump()); } const state = getBreakerState({ numErrors, lastErrorTime, options: this.options }); const stateObject = { numErrors, lastErrorTime, state }; if (state === 1) { this.lastStateTime = Date.now(); this.lastState = stateObject; } return stateObject; } async getState() { if (this.lastStateIsFresh() && this.lastState) { return this.lastState; } return this.getFreshState(); } async shouldAllowRequest() { return (await this.getState()).state !== 1; } async attempt(fn) { const { numErrors, state } = await this.getState(); switch (state) { case 2: case 0: { try { debug4(`breaker(${this.namespace}) => passing through`); const result = await fn(); debug4(`breaker(${this.namespace}) => resetting state: %s`); if (numErrors > 0) { await this.state.reset(); } return result; } catch (err) { await this.state.incr("numErrors"); await this.state.set("lastError", err.message || String(err)); await this.state.set("lastErrorTime", Date.now()); throw err; } } default: { throw new Error(await this.state.get("lastError")); } } } }; function getBreakerState({ numErrors, lastErrorTime, options }) { if (numErrors >= options.maxErrors) { const elapsed = Date.now() - lastErrorTime; if (elapsed >= options.timeout) { return 2; } return 1; } return 0; } // src/breaker/from-sync.ts function fromSync(originalFn, _options) { const options = defaults(_options); const state = { numErrors: 0, lastError: "Unknown error", lastErrorTime: 0 }; if (!(options.store instanceof MemoryStore)) { throw new Error(`Synchronous breakers cannot be distributed. Use an async breaker.`); } return function syncBreaker(...args) { if (state.numErrors >= options.maxErrors && Date.now() - state.lastErrorTime < options.timeout) { throw new Error(state.lastError); } try { const result = originalFn.apply(this, args); state.numErrors = 0; state.lastErrorTime = 0; return result; } catch (err) { ++state.numErrors; state.lastError = String(err.message || err).split("\n")[0]; state.lastErrorTime = Date.now(); throw new Error(state.lastError); } }; } // src/breaker/from-async.ts function fromAsync(originalFn, _options) { const options = defaults(_options); const breaker = new CircuitBreaker(options); return Object.assign(function asyncBreaker(...args) { return breaker.attempt(() => originalFn.apply(this, args)); }, { shouldAllowRequest: () => breaker.shouldAllowRequest() }); } // src/breaker/from-callback.ts function fromCallback(originalFn, _options) { const options = defaults(_options); const breaker = new CircuitBreaker(options); return function callbackBreaker(...args) { const callback = args.pop(); breaker.attempt(() => new Promise((resolve, reject) => { originalFn.call(this, ...args, async function(err, result) { if (err) reject(err); else resolve(result); }); })).then((result) => callback(null, result), (err) => callback(err)); }; } // src/mutex/index.ts var mutex_exports = {}; __export(mutex_exports, { fromAsync: () => fromAsync2, fromGenerator: () => fromGenerator, lock: () => lock }); // src/mutex/mutex.ts var import_uuid2 = __toModule(require("uuid")); // src/errors.ts var COULD_NOT_LOCK = "Unable to obtain lock"; var TIMEOUT = "Operation timed out"; // src/mutex/mutex.ts var TIME_BETWEEN_SPINS = 10; var Mutex = class extends Lock { name; failFast; timeout; store; constructor(options = {}) { super(); this.name = options.name || (0, import_uuid2.v4)(); this.timeout = options.timeout; this.failFast = options.failFast === true; this.store = options.store || new MemoryStore(); } async _tryLock() { this.ref(); const lockId = (0, import_uuid2.v4)(); await this.store.set(this.name, lockId, { notExists: true, expires: this.timeout || 10 * 1e3 }); if (lockId === await this.store.get(this.name)) { return () => { return this.store.del(this.name).then((val) => { this.unref(); return val; }, () => this.unref()); }; } if (this.failFast) { throw new Error(COULD_NOT_LOCK); } } async lock() { const start = Date.now(); const timeout = this.timeout || Infinity; while (Date.now() - start < timeout) { const unlock = await this._tryLock(); if (unlock) { return unlock; } await delay(TIME_BETWEEN_SPINS); } throw new Error(TIMEOUT); } }; // src/mutex/from-async.ts function fromAsync2(fn, options) { const m = new Mutex(options); return async function mutexWrappedFunction(...args) { const unlock = await m.lock(); try { const result = await fn.apply(this, args); unlock(); return result; } catch (err) { unlock(); throw err; } }; } // src/deferral/types.ts var import_debug5 = __toModule(require("debug")); var debug5 = (0, import_debug5.default)("rsxjs"); var DeferredOperation = class { deferred = []; defer(cleanup) { debug5(`registering deferred operation:`, cleanup); this.deferred.push(cleanup); } async cleanup() { for (const cleanup of this.deferred) { debug5(`executing deferred operation:`, cleanup); await cleanup(); } } }; // src/deferral/from-async.ts var import_debug6 = __toModule(require("debug")); var debug6 = (0, import_debug6.default)("rsxjs"); function fromAsync3(fn) { async function deferralWrapper(...args) { const deferral = new DeferredOperation(); try { const retval = await fn.call(this, deferral.defer.bind(deferral), ...args); debug6(`unwinding deferred operations`); await deferral.cleanup(); return retval; } catch (err) { debug6(`unwinding deferred operations (failure)`); Error.captureStackTrace(err, deferralWrapper); await deferral.cleanup(); throw err; } } return deferralWrapper; } // src/coroutine/co.ts function wrap(fn) { function go(...args) { const state = { isCanceled: false, isDone: null, onCancel: [] }; const ctx = this; const p = fromAsync3(async function routine(defer2) { const it = fn.call(ctx, defer2, ...args); if (!it.throw) { throw new Error(`Can only wrap a generator into a coroutine`); } let lastValue; let lastError; while (!state.isCanceled) { const { done, value } = lastError ? it.throw(lastError) : it.next(lastValue); try { lastValue = await value; lastError = void 0; } catch (err) { lastValue = void 0; lastError = err; } if (done) { state.isDone = true; return lastValue; } } state.isDone = false; for (const resolve of state.onCancel) { resolve(state.isDone); } })(); p.cancel = function() { state.isCanceled = true; return new Promise((resolve) => { if (state.isDone !== null) { return resolve(state.isDone); } state.onCancel.push(resolve); }); }; return p; } return go; } function runRoutine(fn) { return wrap(fn)(); } var co = Object.assign(runRoutine, { wrap }); // src/mutex/from-generator.ts function fromGenerator(fn, options) { const m = new Mutex(options); return async function mutexWrappedFunction(...args) { const unlock = await m.lock(); try { return await co(() => fn.apply(this, args)); } finally { unlock(); } }; } // src/mutex/lock.ts async function lock(options) { return { unlock: await new Mutex(options).lock() }; } // src/pool/index.ts var pool_exports = {}; __export(pool_exports, { fromAsyncIterable: () => fromAsyncIterable, fromAsyncIterator: () => fromAsyncIterator, fromIterable: () => fromIterable, fromIterator: () => fromIterator }); // src/pool/from-it.ts var import_assert = __toModule(require("assert")); // src/pool/types.ts function defaults2(_) { const config = {}; return config; } function isWorkerAvailable(worker) { return worker && typeof worker.isAvailable === "function" && worker.isAvailable(); } var GenericPool = class { head; tail; addWorker(worker) { if (!isWorkerAvailable(worker)) { throw new Error(`Worker was given, but is not available.`); } if (!this.tail) { this.head = this.tail = { value: worker }; return; } const node = { prev: this.tail, value: worker }; this.tail.next = node; this.tail = node; } getExistingWorker() { for (let node = this.head; node; node = node.next) { if (isWorkerAvailable(node.value)) { return node.value; } } } }; var SyncPool = class extends GenericPool { constructor(it, options) { super(); this.it = it; this.options = options; } getWorker() { const worker = this.getExistingWorker(); if (worker) return worker; const result = this.it.next(); if (result.done) { throw new Error("Pool has died - iterator is out of results"); } this.addWorker(result.value); return result.value; } }; var AsyncPool = class extends GenericPool { constructor(it, options) { super(); this.it = it; this.options = options; } async getWorker() { const worker = this.getExistingWorker(); if (worker) return worker; const result = await this.it.next(); if (result.done) { throw new Error("Pool has died - iterator is out of results"); } this.addWorker(result.value); return result.value; } }; // src/pool/from-it.ts function fromIterable(iterable, options) { (0, import_assert.ok)(!iterable[Symbol.iterator], "Must provide a valid iterable"); const it = iterable[Symbol.iterator](); return fromIterator(it, options); } function fromIterator(it, _options) { (0, import_assert.ok)(it && typeof it.next === "function", "Must provide a valid iterator"); return new SyncPool(it, defaults2(_options)); } // src/pool/from-async-it.ts var import_assert2 = __toModule(require("assert")); function fromAsyncIterable(iterable, options) { (0, import_assert2.ok)(!iterable[Symbol.asyncIterator], "Must provide a valid iterable"); const it = iterable[Symbol.asyncIterator](); return fromAsyncIterator(it, options); } function fromAsyncIterator(it, _options) { (0, import_assert2.ok)(it && typeof it.next === "function", "Must provide a valid iterator"); return new AsyncPool(it, defaults2(_options)); } // src/semaphore/index.ts var semaphore_exports = {}; __export(semaphore_exports, { fromAsync: () => fromAsync4 }); // src/semaphore/semaphore.ts var import_uuid3 = __toModule(require("uuid")); var Semaphore = class extends Lock { size; mutex; state; constructor(options) { super(); const namespace = `rsxjs:semaphore(${options.name || (0, import_uuid3.v4)()})`; this.size = options.size; this.mutex = new Mutex({ name: `${namespace}:mux`, store: options.store }); this.state = new State({ namespace, store: options.store, defaults: { tokensOut: 0 } }); } async lock(failWithoutLock = false) { const unlock = async () => { const req = this.requests.shift(); if (req) { req.resolve(unlock); return; } await this.state.decr("tokensOut"); }; const releaseMux = await this.mutex.lock(); if (await this.state.get("tokensOut") < this.size) { await this.state.incr("tokensOut"); releaseMux(); return unlock; } releaseMux(); if (failWithoutLock) { throw new Error(COULD_NOT_LOCK); } const d = defer(); this.requests.push({ ...d, unlock }); return d.promise; } }; // src/semaphore/types.ts var DefaultSemaphoreOptions = { size: 0, failFast: false, store: new MemoryStore() }; function defaults3(options) { const config = DefaultSemaphoreOptions; if (options) { if (options.failFast !== void 0) { config.failFast = !!options.failFast; } if (options.size !== void 0) { config.size = options.size; } if (options.store) { config.store = options.store; } if (options.name) { config.name = options.name; } } if (typeof config.size !== "number" || config.size < 1 || Math.round(config.size) !== config.size) { throw new Error(`Invalid size given for semaphore: ${config.size}`); } return config; } // src/semaphore/from-async.ts function fromAsync4(fn, _options) { const options = defaults3(_options); const s = new Semaphore(options); return async function mutexWrappedFunction(...args) { const unlock = await s.lock(options.failFast); try { const result = await fn.apply(this, args); unlock(); return result; } catch (err) { unlock(); throw err; } }; } // src/timeout/index.ts var timeout_exports = {}; __export(timeout_exports, { after: () => after, factory: () => factory, fromAsync: () => fromAsync5, fromPromise: () => fromPromise }); // src/timeout/types.ts var DefaultTimeoutOptions = { timeout: 1e3 }; function defaults4(options) { const config = DefaultTimeoutOptions; if (options) { if (options.timeout !== void 0) { if (typeof options.timeout !== "number" || options.timeout < 0) { throw new Error(`Invalid value given for timeout: ${options.timeout}`); } config.timeout = options.timeout; } else { throw new Error("No timeout option provided! It is required."); } } return config; } // src/timeout/from-promise.ts function fromPromise(promise, _options) { const options = defaults4(_options); return new Promise(async (resolve, reject) => { const timeout = setTimeout(() => { reject(new Error(TIMEOUT)); }, options.timeout); try { const v = await promise; clearTimeout(timeout); resolve(v); } catch (err) { clearTimeout(timeout); reject(err); } }); } // src/timeout/from-async.ts function fromAsync5(cb, options) { return function asyncTimeout(...args) { return fromPromise(cb.apply(this, args), options); }; } function factory(options) { return function fromFactory(fn) { return fromAsync5(fn, options); }; } // src/timeout/after.ts function after(timeout) { const quit = makeChan({ bufferSize: 1 }); setTimeout(() => { quit.put(void 0); quit.close(); }, timeout); return quit; } // src/fallback/index.ts var fallback_exports = {}; __export(fallback_exports, { fromAsync: () => fromAsync6 }); // src/fallback/from-async.ts function fromAsync6(fn, fallback) { return async function fallbackFn(...args) { try { const ret = await fn.apply(this, args); return ret; } catch (err) { const ret = await fallback.apply(this, args); if (typeof ret === "object") { return Object.assign({ isFallback: true, error: err }, ret); } return ret; } }; } // src/deferral/index.ts var deferral_exports = {}; __export(deferral_exports, { fromAsync: () => fromAsync3, fromGenerator: () => fromGenerator2 }); // src/deferral/from-generator.ts function fromGenerator2(fn) { return co.wrap(fn); } // src/waitgroup/waitgroup.ts var WaitGroup = class { numTasks = 0; waiters = []; add(ctr) { if (typeof ctr === "number" || ctr === void 0 || ctr === null) { this.numTasks += ctr; return; } if (typeof ctr === "function") { return this.add(ctr()); } if ("then" in ctr && typeof ctr.then === "function") { this.numTasks++; ctr.then(() => { this.done(); }, (err) => { this.done(err); }); return; } throw new Error(`Unexpected value given to add: ${typeof ctr}`); } done(err) { this.add(-1); if (this.numTasks < 0) { throw new Error(`WaitGroup.done() called too many times`); } if (this.numTasks < 1) { while (this.waiters.length > 0) { const waiter = this.waiters.pop(); if (waiter) { if (err) { waiter.reject(err); } else { waiter.resolve(); } } } } } wait(t) { if (this.numTasks < 1) { return Promise.resolve(); } const d = defer(); this.waiters.push(d); if (t) { const timer = setTimeout(() => { const i = this.waiters.indexOf(d); this.waiters.splice(i, 1); }, t); d.promise.then(() => clearTimeout(timer)); } return d.promise; } }; // src/index.ts if (process.env.NODE_ENV === "test") { Object.assign(exports, { ...utils_exports }); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Breaker, Deferral, Fallback, MemoryStore, Mutex, Pool, RedisStore, Semaphore, Timeout, WaitGroup, co, makeChan, select });