UNPKG

typescript-result

Version:

Supercharge your TypeScript error handling with a powerful Result type that transforms chaotic try-catch blocks into elegant, type-safe code.

513 lines (501 loc) 18.2 kB
function isPromise(value) { if (value === null || value === void 0) { return false; } if (typeof value !== "object") { return false; } return value instanceof Promise || "then" in value; } function isFunction(value) { return typeof value === "function"; } function isAsyncFn(fn) { return fn.constructor.name === "AsyncFunction"; } function isGenerator(obj) { return typeof obj === "object" && obj !== null && typeof obj.next === "function" && typeof obj.throw === "function" && typeof obj.return === "function" && typeof obj[Symbol.iterator] === "function" && obj[Symbol.iterator]() === obj; } function isAsyncGenerator(obj) { return typeof obj === "object" && obj !== null && typeof obj.next === "function" && typeof obj.throw === "function" && typeof obj.return === "function" && typeof obj[Symbol.asyncIterator] === "function" && obj[Symbol.asyncIterator]() === obj; } function assertUnreachable(value) { throw new Error(`Unreachable case: ${value}`); } var NonExhaustiveError = class extends Error { constructor(error) { super("Not all error cases were handled"); this.error = error; } }; var Matcher = class { constructor(error) { this.error = error; } cases=[]; defaultHandler=void 0; when(value, ...args) { const cases = [ value, ...args.slice(0, -1) ]; const handler = args.at(-1); this.cases.push(...cases.map(value2 => ({ value: value2, handler: handler }))); return this; } else=handler => { if (this.defaultHandler) { throw new Error("already registered an 'else' handler"); } this.defaultHandler = handler; return this; }; run=() => { const isAsync = this.cases.some(item => isAsyncFn(item.handler)); for (const item of this.cases) { const match = isFunction(item.value) && this.error instanceof item.value || item.value === this.error; if (match) { const value = item.handler(this.error); return isPromise(value) ? value : isAsync ? Promise.resolve(value) : value; } } if (this.defaultHandler) { return this.defaultHandler(this.error); } throw new NonExhaustiveError(this.error); }; }; var AsyncResult = class _AsyncResult extends Promise { constructor(executor) { super(executor); } * [Symbol.iterator]() { return yield this; } get isAsyncResult() { return true; } async toTuple() { const result = await (this); return result.toTuple(); } async errorOrNull() { const result = await (this); return result.errorOrNull(); } async getOrNull() { const result = await (this); return result.getOrNull(); } async getOrDefault(defaultValue) { const result = await (this); return result.getOrDefault(defaultValue); } async getOrElse(onFailure) { const result = await (this); return result.getOrElse(onFailure); } async getOrThrow() { const result = await (this); return result.getOrThrow(); } async fold(onSuccess, onFailure) { const result = await (this); return result.fold(onSuccess, onFailure); } onFailure(action) { return new _AsyncResult((resolve, reject) => this.then(async result => { try { if (!result.ok) { await action(result.error); } resolve(result); } catch (e) { reject(e); } }).catch(reject)); } onSuccess(action) { return new _AsyncResult((resolve, reject) => this.then(async result => { try { if (result.ok) { await action(result.value); } resolve(result); } catch (error) { reject(error); } }).catch(reject)); } map(transform) { return new _AsyncResult((resolve, reject) => { this.then(async result => resolve(await result.map(transform))).catch(reject); }); } mapCatching(transformValue, transformError) { return new _AsyncResult((resolve, reject) => { this.map(transformValue).then(result => resolve(result)).catch(error => { try { resolve(ResultFactory.error(transformError ? transformError(error) : error)); } catch (err) { reject(err); } }); }); } mapError(transform) { return new _AsyncResult((resolve, reject) => this.then(async result => { try { resolve(result.mapError(transform)); } catch (error) { reject(error); } }).catch(reject)); } recover(onFailure) { return new _AsyncResult((resolve, reject) => this.then(async result => { try { const outcome = await result.recover(onFailure); resolve(outcome); } catch (error) { reject(error); } }).catch(reject)); } recoverCatching(onFailure, transformError) { return new _AsyncResult((resolve, reject) => this.then(result => { resolve(result.recoverCatching(onFailure, transformError)); }).catch(reject)); } toString() { return "AsyncResult"; } static error(error) { return new _AsyncResult(resolve => resolve(ResultFactory.error(error))); } static ok(value) { return new _AsyncResult(resolve => resolve(ResultFactory.ok(value))); } static fromPromise(promise) { return new _AsyncResult((resolve, reject) => { promise.then(value => resolve(ResultFactory.isResult(value) ? value : ResultFactory.ok(value))).catch(reject); }); } static fromPromiseCatching(promise, transform) { return new _AsyncResult(resolve => { promise.then(value => resolve(ResultFactory.isResult(value) ? value : ResultFactory.ok(value))).catch(caughtError => { resolve(ResultFactory.error(transform?.(caughtError) ?? caughtError)); }); }); } }; var Result = class { constructor(_value, _error) { this._value = _value; this._error = _error; } * [Symbol.iterator]() { return yield this; } get isResult() { return true; } get value() { return this._value; } get error() { return this._error; } get success() { return this.error === void 0; } get failure() { return this.error !== void 0; } get ok() { return this.success; } isOk() { return this.success; } isError() { return this.failure; } toTuple() { return [ this._value ?? null, this._error ?? null ]; } errorOrNull() { return this.failure ? this._error : null; } getOrNull() { return this.success ? this._value : null; } getOrDefault(defaultValue) { return this.success ? this._value : defaultValue; } getOrElse(onFailure) { if (isAsyncFn(onFailure)) { return this.success ? Promise.resolve(this._value) : onFailure(this._error); } return this.success ? this._value : onFailure(this._error); } getOrThrow() { if (this.success) { return this._value; } throw this._error; } fold(onSuccess, onFailure) { const isAsync = isAsyncFn(onSuccess) || isAsyncFn(onFailure); const outcome = this.success ? onSuccess(this._value) : onFailure(this._error); return isAsync && !isPromise(outcome) ? Promise.resolve(outcome) : outcome; } match() { return this.failure ? new Matcher(this._error) : void 0; } onFailure(action) { const isAsync = isAsyncFn(action); if (this.failure) { const outcome = action(this._error); if (isAsync) { return new AsyncResult(resolve => { outcome.then(() => resolve(ResultFactory.error(this._error))); }); } return this; } return isAsync ? AsyncResult.ok(this._value) : this; } onSuccess(action) { const isAsync = isAsyncFn(action); if (this.success) { const outcome = action(this._value); if (isAsync) { return new AsyncResult(resolve => { outcome.then(() => resolve(ResultFactory.ok(this._value))); }); } return this; } return isAsync ? AsyncResult.error(this._error) : this; } map(transform) { return this.success ? ResultFactory.run(() => transform(this._value)) : isAsyncFn(transform) ? AsyncResult.error(this._error) : this; } mapCatching(transformValue, transformError) { return this.success ? ResultFactory.try(() => transformValue(this._value), transformError) : this; } mapError(transform) { if (this.success) { return this; } return ResultFactory.error(transform(this._error)); } recover(onFailure) { return this.success ? isAsyncFn(onFailure) ? AsyncResult.ok(this._value) : this : ResultFactory.run(() => onFailure(this._error)); } recoverCatching(onFailure, transformError) { return this.success ? isAsyncFn(onFailure) ? AsyncResult.ok(this._value) : this : ResultFactory.try(() => onFailure(this._error), transformError); } toString() { if (this.success) { return `Result.ok(${this._value})`; } return `Result.error(${this.error})`; } }; var ResultFactory = class _ResultFactory { constructor() {} static ok(value) { return new Result(value, void 0); } static error(error) { return new Result(void 0, error); } static isResult(possibleResult) { return possibleResult instanceof Result; } static isAsyncResult(possibleAsyncResult) { return possibleAsyncResult instanceof AsyncResult; } static run(fn) { const returnValue = fn(); if (isGenerator(returnValue) || isAsyncGenerator(returnValue)) { return _ResultFactory.handleGenerator(returnValue); } if (isPromise(returnValue)) { return AsyncResult.fromPromise(returnValue); } return _ResultFactory.isResult(returnValue) ? returnValue : _ResultFactory.ok(returnValue); } static allInternal(items, opts) { const runner = opts.catching ? _ResultFactory.try : _ResultFactory.run; const flattened = []; let isAsync = items.some(isPromise); let hasFailure = false; for (const item of items) { if (isFunction(item)) { if (hasFailure) { continue; } const returnValue = runner(item); if (_ResultFactory.isResult(returnValue) && !returnValue.ok) { hasFailure = true; if (!isAsync) { return returnValue; } } if (_ResultFactory.isAsyncResult(returnValue)) { isAsync = true; } flattened.push(returnValue); } else if (_ResultFactory.isResult(item)) { if (!item.ok) { hasFailure = true; if (!isAsync) { return item; } } flattened.push(item); } else if (_ResultFactory.isAsyncResult(item)) { isAsync = true; flattened.push(item); } else if (isPromise(item)) { isAsync = true; flattened.push(opts.catching ? AsyncResult.fromPromiseCatching(item) : AsyncResult.fromPromise(item)); } else { flattened.push(_ResultFactory.ok(item)); } } if (isAsync) { return new AsyncResult((resolve, reject) => { const asyncResults = []; const asyncIndexes = []; for (let i = 0; i < flattened.length; i++) { const item = flattened[i]; if (_ResultFactory.isAsyncResult(item)) { asyncResults.push(item); asyncIndexes.push(i); } } Promise.all(asyncResults).then(resolvedResults => { const merged = [ ...flattened ]; for (let i = 0; i < resolvedResults.length; i++) { merged[asyncIndexes[i]] = resolvedResults[i]; } const firstFailedResult = merged.find(resolvedResult => !resolvedResult.ok); if (firstFailedResult) { resolve(firstFailedResult); return; } resolve(_ResultFactory.ok(merged.map(result => result.getOrNull()))); }).catch(reason => { reject(reason); }); }); } return _ResultFactory.ok(flattened.map(result => result.getOrNull())); } static all(...items) { return _ResultFactory.allInternal(items, { catching: false }); } static allCatching(...items) { return _ResultFactory.allInternal(items, { catching: true }); } static wrap(fn, transformError) { return function wrapped(...args) { return _ResultFactory.try(() => fn(...args), transformError); }; } static try(fn, transform) { try { const returnValue = fn(); if (isGenerator(returnValue)) { return _ResultFactory.handleGenerator(returnValue); } if (isAsyncGenerator(returnValue)) { const asyncResult = _ResultFactory.handleGenerator(returnValue); return AsyncResult.fromPromiseCatching(asyncResult, transform); } if (isPromise(returnValue)) { return AsyncResult.fromPromiseCatching(returnValue, transform); } return _ResultFactory.isResult(returnValue) ? returnValue : _ResultFactory.ok(returnValue); } catch (caughtError) { return _ResultFactory.error(transform?.(caughtError) ?? caughtError); } } static fromAsync(valueOrFn) { return _ResultFactory.run(typeof valueOrFn === "function" ? valueOrFn : () => valueOrFn); } static fromAsyncCatching(valueOrFn, transformError) { return _ResultFactory.try(typeof valueOrFn === "function" ? valueOrFn : () => valueOrFn, transformError); } static handleGenerator(it) { function handleResult(result2) { if (!result2.ok) { return iterate(it.return(result2)); } return iterate(it.next(result2.value)); } function handleStep(step) { if (step.done) { if (step.value instanceof Result || step.value instanceof AsyncResult) { return step.value; } return _ResultFactory.ok(step.value); } if (step.value instanceof Result) { return handleResult(step.value); } if (step.value instanceof AsyncResult) { return step.value.then(handleResult); } return iterate(it.next(step.value)); } function iterate(iteratorResult) { return isPromise(iteratorResult) ? iteratorResult.then(handleStep) : handleStep(iteratorResult); } const result = iterate(it.next()); return isPromise(result) ? AsyncResult.fromPromise(result) : result; } static gen(generatorOrSelfOrFn, fn) { const it = isGenerator(generatorOrSelfOrFn) || isAsyncGenerator(generatorOrSelfOrFn) ? generatorOrSelfOrFn : typeof generatorOrSelfOrFn === "function" ? generatorOrSelfOrFn() : fn?.apply(generatorOrSelfOrFn); return _ResultFactory.handleGenerator(it); } static genCatching(generatorOrSelfOrFn, transformValueOrError, transformError) { const isGen = isGenerator(generatorOrSelfOrFn) || isAsyncGenerator(generatorOrSelfOrFn); const self = typeof generatorOrSelfOrFn === "function" || isGen ? void 0 : generatorOrSelfOrFn; const tValue = typeof generatorOrSelfOrFn === "function" ? generatorOrSelfOrFn : transformValueOrError; const tError = typeof generatorOrSelfOrFn === "function" || isGen ? transformValueOrError : transformError; try { const it = isGen ? generatorOrSelfOrFn : self ? tValue.apply(generatorOrSelfOrFn) : tValue(); const result = _ResultFactory.handleGenerator(it); if (_ResultFactory.isAsyncResult(result)) { return result.catch(error => AsyncResult.error(tError?.(error) ?? error)); } return result; } catch (error) { return _ResultFactory.error(tError?.(error) ?? error); } } static assertOk(result) { if (!result.ok) { throw new Error("Expected a successful result, but got an error instead"); } } static assertError(result) { if (result.ok) { throw new Error("Expected a failed result, but got a value instead"); } } static [Symbol.hasInstance](instance) { return instance instanceof Result; } }; var Result2 = ResultFactory; export { AsyncResult, NonExhaustiveError, Result2 as Result, assertUnreachable };//# sourceMappingURL=index.js.map