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
JavaScript
"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