UNPKG

veffect

Version:

powerful TypeScript validation library built on the robust foundation of Effect combining exceptional type safety, high performance, and developer experience. Taking inspiration from Effect's functional principles, VEffect delivers a balanced approach tha

296 lines (295 loc) 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeWithTTL = exports.make = exports.isPool = exports.invalidate = exports.get = exports.PoolTypeId = void 0; var Context = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../Context.js")); var Duration = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../Duration.js")); var Equal = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../Equal.js")); var _Function = /*#__PURE__*/require("../Function.js"); var Hash = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../Hash.js")); var HashSet = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../HashSet.js")); var _Pipeable = /*#__PURE__*/require("../Pipeable.js"); var _Predicate = /*#__PURE__*/require("../Predicate.js"); var effect = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./core-effect.js")); var core = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./core.js")); var fiberRuntime = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./fiberRuntime.js")); var queue = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./queue.js")); var ref = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./ref.js")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** @internal */ const PoolSymbolKey = "effect/Pool"; /** @internal */ const PoolTypeId = exports.PoolTypeId = /*#__PURE__*/Symbol.for(PoolSymbolKey); const poolVariance = { /* c8 ignore next */ _E: _ => _, /* c8 ignore next */ _A: _ => _ }; /** * A strategy that does nothing to shrink excess items. This is useful when * the minimum size of the pool is equal to its maximum size and so there is * nothing to do. */ class NoneStrategy { initial() { return core.unit; } track() { return core.unit; } run() { return core.unit; } } /** * A strategy that shrinks the pool down to its minimum size if items in the * pool have not been used for the specified duration. */ class TimeToLiveStrategy { timeToLive; constructor(timeToLive) { this.timeToLive = timeToLive; } initial() { return core.flatMap(effect.clock, clock => core.flatMap(clock.currentTimeMillis, now => core.map(ref.make(now), ref => [clock, ref]))); } track(state) { return core.asUnit(core.flatMap(state[0].currentTimeMillis, now => ref.set(state[1], now))); } run(state, getExcess, shrink) { return core.flatMap(getExcess, excess => excess <= 0 ? core.zipRight(state[0].sleep(this.timeToLive), this.run(state, getExcess, shrink)) : (0, _Function.pipe)(core.zipWith(ref.get(state[1]), state[0].currentTimeMillis, (start, end) => end - start), core.flatMap(duration => { if (duration >= Duration.toMillis(this.timeToLive)) { return core.zipRight(shrink, this.run(state, getExcess, shrink)); } else { return core.zipRight(state[0].sleep(this.timeToLive), this.run(state, getExcess, shrink)); } }))); } } class PoolImpl { creator; min; max; isShuttingDown; state; items; invalidated; track; [PoolTypeId] = poolVariance; constructor(creator, min, max, isShuttingDown, state, items, invalidated, track) { this.creator = creator; this.min = min; this.max = max; this.isShuttingDown = isShuttingDown; this.state = state; this.items = items; this.invalidated = invalidated; this.track = track; } [Hash.symbol]() { return (0, _Function.pipe)(Hash.hash(this.creator), Hash.combine(Hash.number(this.min)), Hash.combine(Hash.number(this.max)), Hash.combine(Hash.hash(this.isShuttingDown)), Hash.combine(Hash.hash(this.state)), Hash.combine(Hash.hash(this.items)), Hash.combine(Hash.hash(this.invalidated)), Hash.combine(Hash.hash(this.track)), Hash.cached(this)); } [Equal.symbol](that) { return isPool(that) && Equal.equals(this.creator, that.creator) && this.min === that.min && this.max === that.max && Equal.equals(this.isShuttingDown, that.isShuttingDown) && Equal.equals(this.state, that.state) && Equal.equals(this.items, that.items) && Equal.equals(this.invalidated, that.invalidated) && Equal.equals(this.track, that.track); } pipe() { return (0, _Pipeable.pipeArguments)(this, arguments); } get get() { const acquire = restore => core.flatMap(ref.get(this.isShuttingDown), down => down ? core.interrupt : core.flatten(ref.modify(this.state, state => { if (state.free > 0 || state.size >= this.max) { return [core.flatMap(queue.take(this.items), attempted => core.exitMatch(attempted.result, { onFailure: () => core.succeed(attempted), onSuccess: item => core.flatMap(ref.get(this.invalidated), set => { if ((0, _Function.pipe)(set, HashSet.has(item))) { return core.zipRight(finalizeInvalid(this, attempted), acquire(restore)); } return core.succeed(attempted); }) })), { ...state, free: state.free - 1 }]; } if (state.size >= 0) { return [core.zipRight(allocate(this, restore), acquire(restore)), { size: state.size + 1, free: state.free + 1 }]; } return [core.interrupt, state]; }))); const release = attempted => core.exitMatch(attempted.result, { onFailure: () => core.flatten(ref.modify(this.state, state => { if (state.size <= this.min) { return [allocateUinterruptible(this), { ...state, free: state.free + 1 }]; } return [core.unit, { ...state, size: state.size - 1 }]; })), onSuccess: item => core.flatMap(ref.get(this.invalidated), set => { if ((0, _Function.pipe)(set, HashSet.has(item))) { return finalizeInvalid(this, attempted); } return (0, _Function.pipe)(ref.update(this.state, state => ({ ...state, free: state.free + 1 })), core.zipRight(queue.offer(this.items, attempted)), core.zipRight(this.track(attempted.result)), core.zipRight(core.whenEffect(getAndShutdown(this), ref.get(this.isShuttingDown)))); }) }); return (0, _Function.pipe)(core.uninterruptibleMask(restore => core.tap(acquire(restore), a => fiberRuntime.addFinalizer(_exit => release(a)))), fiberRuntime.withEarlyRelease, fiberRuntime.disconnect, core.flatMap(([release, attempted]) => (0, _Function.pipe)(effect.when(release, () => isFailure(attempted)), core.zipRight(toEffect(attempted))))); } invalidate(item) { return ref.update(this.invalidated, HashSet.add(item)); } } const allocate = (self, restore) => core.flatMap(fiberRuntime.scopeMake(), scope => core.flatMap(core.exit(restore(fiberRuntime.scopeExtend(self.creator, scope))), exit => core.flatMap(core.succeed({ result: exit, finalizer: core.scopeClose(scope, core.exitSucceed(void 0)) }), attempted => (0, _Function.pipe)(queue.offer(self.items, attempted), core.zipRight(self.track(attempted.result)), core.zipRight(core.whenEffect(getAndShutdown(self), ref.get(self.isShuttingDown))), core.as(attempted))))); const allocateUinterruptible = self => core.uninterruptibleMask(restore => allocate(self, restore)); /** * Returns the number of items in the pool in excess of the minimum size. */ const excess = self => core.map(ref.get(self.state), state => state.size - Math.min(self.min, state.free)); const finalizeInvalid = (self, attempted) => (0, _Function.pipe)(forEach(attempted, a => ref.update(self.invalidated, HashSet.remove(a))), core.zipRight(attempted.finalizer), core.zipRight(core.flatten(ref.modify(self.state, state => { if (state.size <= self.min) { return [allocateUinterruptible(self), { ...state, free: state.free + 1 }]; } return [core.unit, { ...state, size: state.size - 1 }]; })))); /** * Gets items from the pool and shuts them down as long as there are items * free, signalling shutdown of the pool if the pool is empty. */ const getAndShutdown = self => core.flatten(ref.modify(self.state, state => { if (state.free > 0) { return [core.matchCauseEffect(queue.take(self.items), { onFailure: () => core.unit, onSuccess: attempted => (0, _Function.pipe)(forEach(attempted, a => ref.update(self.invalidated, HashSet.remove(a))), core.zipRight(attempted.finalizer), core.zipRight(ref.update(self.state, state => ({ ...state, size: state.size - 1 }))), core.flatMap(() => getAndShutdown(self))) }), { ...state, free: state.free - 1 }]; } if (state.size > 0) { return [core.unit, state]; } return [queue.shutdown(self.items), { ...state, size: state.size - 1 }]; })); /** * Begins pre-allocating pool entries based on minimum pool size. */ const initialize = self => fiberRuntime.replicateEffect(core.uninterruptibleMask(restore => core.flatten(ref.modify(self.state, state => { if (state.size < self.min && state.size >= 0) { return [allocate(self, restore), { size: state.size + 1, free: state.free + 1 }]; } return [core.unit, state]; }))), self.min, { discard: true }); /** * Shrinks the pool down, but never to less than the minimum size. */ const shrink = self => core.uninterruptible(core.flatten(ref.modify(self.state, state => { if (state.size > self.min && state.free > 0) { return [(0, _Function.pipe)(queue.take(self.items), core.flatMap(attempted => (0, _Function.pipe)(forEach(attempted, a => ref.update(self.invalidated, HashSet.remove(a))), core.zipRight(attempted.finalizer), core.zipRight(ref.update(self.state, state => ({ ...state, size: state.size - 1 })))))), { ...state, free: state.free - 1 }]; } return [core.unit, state]; }))); const shutdown = self => core.flatten(ref.modify(self.isShuttingDown, down => down ? [queue.awaitShutdown(self.items), true] : [core.zipRight(getAndShutdown(self), queue.awaitShutdown(self.items)), true])); const isFailure = self => core.exitIsFailure(self.result); const forEach = (self, f) => core.exitMatch(self.result, { onFailure: () => core.unit, onSuccess: f }); const toEffect = self => self.result; /** * A more powerful variant of `make` that allows specifying a `Strategy` that * describes how a pool whose excess items are not being used will be shrunk * down to the minimum size. */ const makeWith = options => core.uninterruptibleMask(restore => (0, _Function.pipe)(fiberRuntime.all([core.context(), ref.make(false), ref.make({ size: 0, free: 0 }), queue.bounded(options.max), ref.make(HashSet.empty()), options.strategy.initial()]), core.flatMap(([context, down, state, items, inv, initial]) => { const pool = new PoolImpl(core.mapInputContext(options.acquire, old => Context.merge(old)(context)), options.min, options.max, down, state, items, inv, exit => options.strategy.track(initial, exit)); return (0, _Function.pipe)(fiberRuntime.forkDaemon(restore(initialize(pool))), core.flatMap(fiber => core.flatMap(fiberRuntime.forkDaemon(restore(options.strategy.run(initial, excess(pool), shrink(pool)))), shrink => fiberRuntime.addFinalizer(() => (0, _Function.pipe)(shutdown(pool), core.zipRight(core.interruptFiber(fiber)), core.zipRight(core.interruptFiber(shrink)))))), core.as(pool)); }))); /** @internal */ const isPool = u => (0, _Predicate.hasProperty)(u, PoolTypeId); /** @internal */ exports.isPool = isPool; const make = options => makeWith({ acquire: options.acquire, min: options.size, max: options.size, strategy: new NoneStrategy() }); /** @internal */ exports.make = make; const makeWithTTL = options => makeWith({ acquire: options.acquire, min: options.min, max: options.max, strategy: new TimeToLiveStrategy(Duration.decode(options.timeToLive)) }); /** @internal */ exports.makeWithTTL = makeWithTTL; const get = self => self.get; /** @internal */ exports.get = get; const invalidate = exports.invalidate = /*#__PURE__*/(0, _Function.dual)(2, (self, value) => self.invalidate(value)); //# sourceMappingURL=pool.js.map