mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-29 12:57:50 +08:00
reconstruct: autocraft algorithm; feature: rust-style result
reconstruct: - move queue and sortedarray to dir datatype - move semaphore and readwritelock to dir mutex - reconstruct autocraft search algorithm, use hashmap instead of forloop - adjust some code style feature: - add rust-style result lib
This commit is contained in:
@@ -4,7 +4,7 @@ import { createAccessControlCLI } from "./cli";
|
||||
import { launchAccessControlTUI } from "./tui";
|
||||
import * as peripheralManager from "../lib/PeripheralManager";
|
||||
import { deepCopy } from "@/lib/common";
|
||||
import { ReadWriteLock } from "@/lib/ReadWriteLock";
|
||||
import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock";
|
||||
|
||||
const args = [...$vararg];
|
||||
|
||||
|
||||
@@ -1,60 +1,62 @@
|
||||
import { CraftManager } from "@/lib/CraftManager";
|
||||
import * as peripheralManager from "../lib/PeripheralManager";
|
||||
import { CCLog } from "@/lib/ccLog";
|
||||
|
||||
const log = new CCLog("autocraft.log");
|
||||
const logger = new CCLog("autocraft.log");
|
||||
|
||||
const peripheralsRelativeSides = {
|
||||
const peripheralsNames = {
|
||||
packagesContainer: "minecraft:chest_10",
|
||||
itemsContainer: "minecraft:chest_9",
|
||||
packageExtractor: "create:packager_1",
|
||||
blockReader: "front",
|
||||
wiredModem: "back",
|
||||
redstone: "front",
|
||||
};
|
||||
|
||||
const packagesContainer = peripheral.wrap(
|
||||
peripheralsNames.packagesContainer,
|
||||
) as InventoryPeripheral;
|
||||
const itemsContainer = peripheral.wrap(
|
||||
peripheralsNames.itemsContainer,
|
||||
) as InventoryPeripheral;
|
||||
const packageExtractor = peripheral.wrap(
|
||||
peripheralsNames.packageExtractor,
|
||||
) as InventoryPeripheral;
|
||||
const blockReader = peripheral.wrap(
|
||||
peripheralsNames.blockReader,
|
||||
) as BlockReaderPeripheral;
|
||||
const wiredModem = peripheral.wrap(
|
||||
peripheralsNames.wiredModem,
|
||||
) as WiredModemPeripheral;
|
||||
const turtleLocalName = wiredModem.getNameLocal();
|
||||
|
||||
enum State {
|
||||
IDLE,
|
||||
CHECK_PACK,
|
||||
READ_RECIPE,
|
||||
PULL_ITEMS,
|
||||
CRAFT_OUTPUT,
|
||||
}
|
||||
|
||||
function main() {
|
||||
const packagesContainer = peripheralManager.findByNameRequired(
|
||||
"inventory",
|
||||
peripheralsRelativeSides.packagesContainer,
|
||||
);
|
||||
|
||||
const itemsContainer = peripheralManager.findByNameRequired(
|
||||
"inventory",
|
||||
peripheralsRelativeSides.itemsContainer,
|
||||
);
|
||||
|
||||
const packageExtractor = peripheralManager.findByNameRequired(
|
||||
"inventory",
|
||||
peripheralsRelativeSides.packageExtractor,
|
||||
);
|
||||
|
||||
const blockReader = peripheralManager.findByNameRequired(
|
||||
"blockReader",
|
||||
peripheralsRelativeSides.blockReader,
|
||||
);
|
||||
|
||||
const wiredModem = peripheralManager.findByNameRequired(
|
||||
"wiredModem",
|
||||
peripheralsRelativeSides.wiredModem,
|
||||
);
|
||||
const turtleLocalName = wiredModem.getNameLocal();
|
||||
|
||||
const craftManager = new CraftManager(turtleLocalName);
|
||||
let hasPackage = redstone.getInput(peripheralsNames.redstone);
|
||||
// let currentState = State.IDLE;
|
||||
// let nextState = State.IDLE;
|
||||
|
||||
let hasPackage = redstone.getInput("front");
|
||||
logger.info("AutoCraft init finished...");
|
||||
while (true) {
|
||||
if (!hasPackage) os.pullEvent("redstone");
|
||||
hasPackage = redstone.getInput("front");
|
||||
hasPackage = redstone.getInput(peripheralsNames.redstone);
|
||||
if (!hasPackage) {
|
||||
continue;
|
||||
}
|
||||
log.info(`Package detected`);
|
||||
logger.info(`Package detected`);
|
||||
|
||||
const itemsInfo = packagesContainer.list();
|
||||
for (const key in itemsInfo) {
|
||||
const slot = parseInt(key);
|
||||
const item = itemsInfo[slot];
|
||||
log.info(`${item.count}x ${item.name} in slot ${key}`);
|
||||
logger.info(`${item.count}x ${item.name} in slot ${key}`);
|
||||
|
||||
// Get package NBT
|
||||
packagesContainer.pushItems(turtleLocalName, slot);
|
||||
@@ -65,9 +67,9 @@ function main() {
|
||||
const packageRecipes = CraftManager.getPackageRecipe(packageInfo);
|
||||
|
||||
// No recipe, just extract package
|
||||
if (packageRecipes == undefined) {
|
||||
if (packageRecipes.isNone()) {
|
||||
packageExtractor.pullItems(turtleLocalName, 1);
|
||||
log.info(`No recipe, just pass`);
|
||||
logger.info(`No recipe, just pass`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -76,7 +78,7 @@ function main() {
|
||||
packageExtractor.pullItems(turtleLocalName, 1);
|
||||
|
||||
// Pull and craft multi recipe
|
||||
for (const recipe of packageRecipes) {
|
||||
for (const recipe of packageRecipes.value) {
|
||||
let craftOutputItem: BlockItemDetailData | undefined = undefined;
|
||||
let restCraftCnt = recipe.Count;
|
||||
|
||||
@@ -84,15 +86,17 @@ function main() {
|
||||
// Clear workbench
|
||||
craftManager.pushAll(itemsContainer);
|
||||
|
||||
log.info(`Pull items according to a recipe`);
|
||||
const craftCnt = craftManager.pullItems(
|
||||
recipe,
|
||||
itemsContainer,
|
||||
restCraftCnt,
|
||||
);
|
||||
logger.info(`Pull items according to a recipe`);
|
||||
const craftCnt = craftManager
|
||||
.pullItems(recipe, itemsContainer, restCraftCnt)
|
||||
.unwrapOrElse((error) => {
|
||||
logger.error(error.message);
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (craftCnt == 0) break;
|
||||
craftManager.craft();
|
||||
log.info(`Craft ${craftCnt} times`);
|
||||
logger.info(`Craft ${craftCnt} times`);
|
||||
restCraftCnt -= craftCnt;
|
||||
|
||||
// Get output item
|
||||
@@ -101,9 +105,9 @@ function main() {
|
||||
|
||||
// Finally output
|
||||
if (restCraftCnt > 0) {
|
||||
log.warn(`Only craft ${recipe.Count - restCraftCnt} times`);
|
||||
logger.warn(`Only craft ${recipe.Count - restCraftCnt} times`);
|
||||
} else {
|
||||
log.info(`Finish craft ${recipe.Count}x ${craftOutputItem?.id}`);
|
||||
logger.info(`Finish craft ${recipe.Count}x ${craftOutputItem?.id}`);
|
||||
}
|
||||
craftManager.pushAll(itemsContainer);
|
||||
}
|
||||
@@ -114,7 +118,7 @@ function main() {
|
||||
try {
|
||||
main();
|
||||
} catch (error: unknown) {
|
||||
log.error(textutils.serialise(error as object));
|
||||
logger.error(textutils.serialise(error as object));
|
||||
} finally {
|
||||
log.close();
|
||||
logger.close();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CCLog } from "./ccLog";
|
||||
|
||||
const log = new CCLog("CraftManager.log");
|
||||
import { Queue } from "./datatype/Queue";
|
||||
import { Result, Ok, Err, Option, Some, None } from "./thirdparty/ts-result-es";
|
||||
|
||||
// ComputerCraft Turtle inventory layout:
|
||||
// 1, 2, 3, 4
|
||||
@@ -70,6 +69,13 @@ interface CraftRecipe {
|
||||
Count: number;
|
||||
}
|
||||
|
||||
interface InventorySlotInfo {
|
||||
name: string;
|
||||
count: number;
|
||||
maxCount: number;
|
||||
slotNum: number;
|
||||
}
|
||||
|
||||
type CraftMode = "keep" | "keepProduct" | "keepIngredient";
|
||||
|
||||
class CraftManager {
|
||||
@@ -117,80 +123,122 @@ class CraftManager {
|
||||
|
||||
public static getPackageRecipe(
|
||||
item: BlockItemDetailData,
|
||||
): CraftRecipe[] | undefined {
|
||||
): Option<CraftRecipe[]> {
|
||||
if (
|
||||
!item.id.includes("create:cardboard_package") ||
|
||||
(item.tag as CreatePackageTag)?.Fragment?.OrderContext
|
||||
?.OrderedCrafts?.[0] == undefined
|
||||
) {
|
||||
return undefined;
|
||||
return None;
|
||||
}
|
||||
|
||||
const orderedCraft = (item.tag as CreatePackageTag).Fragment.OrderContext
|
||||
.OrderedCrafts;
|
||||
return orderedCraft.map((value, _) => ({
|
||||
PatternEntries: value.Pattern.Entries,
|
||||
Count: value.Count,
|
||||
}));
|
||||
return new Some(
|
||||
orderedCraft.map((value, _) => ({
|
||||
PatternEntries: value.Pattern.Entries,
|
||||
Count: value.Count,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
public pullItems(
|
||||
recipe: CraftRecipe,
|
||||
inventory: InventoryPeripheral,
|
||||
limit: number,
|
||||
): number {
|
||||
let maxCraftCount = limit;
|
||||
srcInventory: InventoryPeripheral,
|
||||
craftCnt: number,
|
||||
): Result<number> {
|
||||
// Initialize hash map
|
||||
const ingredientList = srcInventory.list();
|
||||
const ingredientMap = new Map<string, Queue<InventorySlotInfo>>();
|
||||
for (const key in ingredientList) {
|
||||
const slotNum = parseInt(key);
|
||||
const item = srcInventory.getItemDetail(slotNum)!;
|
||||
|
||||
if (ingredientMap.has(item.name)) {
|
||||
ingredientMap.get(item.name)!.enqueue({
|
||||
name: item.name,
|
||||
slotNum: slotNum,
|
||||
count: item.count,
|
||||
maxCount: item.maxCount,
|
||||
});
|
||||
} else {
|
||||
ingredientMap.set(
|
||||
item.name,
|
||||
new Queue<InventorySlotInfo>([
|
||||
{
|
||||
name: item.name,
|
||||
slotNum: slotNum,
|
||||
count: item.count,
|
||||
maxCount: item.maxCount,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let maxCraftCnt = craftCnt;
|
||||
for (const index in recipe.PatternEntries) {
|
||||
const entry = recipe.PatternEntries[index];
|
||||
if (entry.Item.Count == 0 || entry.Item.id == "minecraft:air") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ingredientList = inventory.list();
|
||||
let restCount = maxCraftCount;
|
||||
for (const key in ingredientList) {
|
||||
// Get item detail and check max count
|
||||
const slot = parseInt(key);
|
||||
const ingredient = inventory.getItemDetail(slot)!;
|
||||
if (entry.Item.id != ingredient.name) {
|
||||
continue;
|
||||
if (!ingredientMap.has(entry.Item.id))
|
||||
return new Err(Error(`No ingredient match ${entry.Item.id}`));
|
||||
|
||||
const ingredient = ingredientMap.get(entry.Item.id)!;
|
||||
let restCraftCnt = maxCraftCnt;
|
||||
while (restCraftCnt > 0 && ingredient.size() > 0) {
|
||||
const slotItem = ingredient.dequeue()!;
|
||||
|
||||
// Check item max stack count
|
||||
if (slotItem.maxCount < maxCraftCnt) {
|
||||
maxCraftCnt = slotItem.maxCount;
|
||||
restCraftCnt = maxCraftCnt;
|
||||
}
|
||||
|
||||
const ingredientMaxCount = ingredient.maxCount;
|
||||
if (maxCraftCount > ingredientMaxCount) {
|
||||
maxCraftCount = ingredientMaxCount;
|
||||
restCount = maxCraftCount;
|
||||
}
|
||||
log.info(
|
||||
`Slot ${slot} ${ingredient.name} max count: ${ingredientMaxCount}`,
|
||||
);
|
||||
|
||||
// TODO: Process multi count entry item
|
||||
if (ingredient.count >= restCount) {
|
||||
inventory.pushItems(
|
||||
if (slotItem.count >= restCraftCnt) {
|
||||
const pushItemsCnt = srcInventory.pushItems(
|
||||
this.localName,
|
||||
slot,
|
||||
restCount,
|
||||
CRAFT_SLOT_TABLE[parseInt(index) - 1],
|
||||
slotItem.slotNum,
|
||||
restCraftCnt,
|
||||
CRAFT_SLOT_TABLE[index],
|
||||
);
|
||||
restCount = 0;
|
||||
break;
|
||||
if (pushItemsCnt !== restCraftCnt)
|
||||
return new Err(
|
||||
Error(
|
||||
`Try to get items ${restCraftCnt}x "${slotItem.name}" from inventory, but only get ${pushItemsCnt}x`,
|
||||
),
|
||||
);
|
||||
if (slotItem.count > restCraftCnt) {
|
||||
ingredient.enqueue({
|
||||
...slotItem,
|
||||
count: slotItem.count - restCraftCnt,
|
||||
});
|
||||
}
|
||||
restCraftCnt = 0;
|
||||
} else {
|
||||
inventory.pushItems(
|
||||
const pushItemsCnt = srcInventory.pushItems(
|
||||
this.localName,
|
||||
slot,
|
||||
ingredient.count,
|
||||
CRAFT_SLOT_TABLE[parseInt(index) - 1],
|
||||
slotItem.slotNum,
|
||||
slotItem.count,
|
||||
CRAFT_SLOT_TABLE[index],
|
||||
);
|
||||
restCount -= ingredient.count;
|
||||
if (pushItemsCnt !== slotItem.count)
|
||||
return new Err(
|
||||
Error(
|
||||
`Try to get items ${slotItem.count}x "${slotItem.name}" from inventory, but only get ${pushItemsCnt}x`,
|
||||
),
|
||||
);
|
||||
restCraftCnt -= slotItem.count;
|
||||
}
|
||||
}
|
||||
|
||||
if (restCount > 0) return 0;
|
||||
if (restCraftCnt > 0)
|
||||
return new Err(Error("Not enough items in inventory"));
|
||||
}
|
||||
|
||||
return maxCraftCount;
|
||||
return new Ok(maxCraftCnt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export function concatSentence(words: string[], length: number): string[] {
|
||||
* @see Source project, ts-deepcopy https://github.com/ykdr2017/ts-deepcopy
|
||||
* @see Code pen https://codepen.io/erikvullings/pen/ejyBYg
|
||||
*/
|
||||
export const deepCopy = <T>(target: T): T => {
|
||||
export function deepCopy<T>(target: T): T {
|
||||
if (target === null) {
|
||||
return target;
|
||||
}
|
||||
@@ -48,4 +48,4 @@ export const deepCopy = <T>(target: T): T => {
|
||||
return cp as T;
|
||||
}
|
||||
return target;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export class Node<T> {
|
||||
public value: T;
|
||||
public next?: Node<T>
|
||||
public prev?: Node<T>
|
||||
public next?: Node<T>;
|
||||
public prev?: Node<T>;
|
||||
|
||||
constructor(value: T, next?: Node<T>, prev?: Node<T>) {
|
||||
this.value = value;
|
||||
@@ -15,10 +15,15 @@ export class Queue<T> {
|
||||
private _tail?: Node<T>;
|
||||
private _size: number;
|
||||
|
||||
constructor() {
|
||||
constructor(data?: T[]) {
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
this._size = 0;
|
||||
|
||||
if (data === undefined) return;
|
||||
for (const item of data) {
|
||||
this.enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public enqueue(data: T): void {
|
||||
@@ -37,11 +42,11 @@ export class Queue<T> {
|
||||
}
|
||||
|
||||
public dequeue(): T | undefined {
|
||||
if (this._head === undefined) return undefined;
|
||||
const node = this._head;
|
||||
if (node === undefined) return undefined;
|
||||
|
||||
this._head = this._head!.next;
|
||||
this._head!.prev = undefined;
|
||||
this._head = node.next;
|
||||
if (this._head !== undefined) this._head.prev = undefined;
|
||||
this._size--;
|
||||
|
||||
return node.value;
|
||||
@@ -68,8 +73,7 @@ export class Queue<T> {
|
||||
const array: T[] = [];
|
||||
let currentNode: Node<T> = this._head!;
|
||||
for (let i = 0; i < this._size; i++) {
|
||||
if (currentNode.value !== undefined)
|
||||
array.push(currentNode.value);
|
||||
if (currentNode.value !== undefined) array.push(currentNode.value);
|
||||
|
||||
currentNode = currentNode.next!;
|
||||
}
|
||||
@@ -82,13 +86,13 @@ export class Queue<T> {
|
||||
return {
|
||||
next(): IteratorResult<T> {
|
||||
if (currentNode === undefined) {
|
||||
return { value: undefined, done: true }
|
||||
return { value: undefined, done: true };
|
||||
} else {
|
||||
const data = currentNode.value;
|
||||
currentNode = currentNode.next;
|
||||
return { value: data, done: false }
|
||||
return { value: data, done: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SortedArray } from "./SortedArray";
|
||||
import { SortedArray } from "../datatype/SortedArray";
|
||||
|
||||
const E_CANCELED = new Error("Request canceled");
|
||||
// const E_INSUFFICIENT_RESOURCES = new Error("Insufficient resources");
|
||||
21
src/lib/thirdparty/ts-result-es/LICENSE
vendored
Normal file
21
src/lib/thirdparty/ts-result-es/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 vultix
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2
src/lib/thirdparty/ts-result-es/index.ts
vendored
Normal file
2
src/lib/thirdparty/ts-result-es/index.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./result";
|
||||
export * from "./option";
|
||||
343
src/lib/thirdparty/ts-result-es/option.ts
vendored
Normal file
343
src/lib/thirdparty/ts-result-es/option.ts
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
import { toString } from "./utils";
|
||||
// import { Result, Ok, Err } from "./result";
|
||||
|
||||
interface BaseOption<T> extends Iterable<T> {
|
||||
/** `true` when the Option is Some */
|
||||
isSome(): this is SomeImpl<T>;
|
||||
|
||||
/** `true` when the Option is None */
|
||||
isNone(): this is None;
|
||||
|
||||
/**
|
||||
* Returns the contained `Some` value, if exists. Throws an error if not.
|
||||
*
|
||||
* If you know you're dealing with `Some` and the compiler knows it too (because you tested
|
||||
* `isSome()` or `isNone()`) you should use `value` instead. While `Some`'s `expect()` and `value` will
|
||||
* both return the same value using `value` is preferable because it makes it clear that
|
||||
* there won't be an exception thrown on access.
|
||||
*
|
||||
* @param msg the message to throw if no Some value.
|
||||
*/
|
||||
expect(msg: string): T;
|
||||
|
||||
/**
|
||||
* Returns the contained `Some` value.
|
||||
* Because this function may throw, its use is generally discouraged.
|
||||
* Instead, prefer to handle the `None` case explicitly.
|
||||
*
|
||||
* If you know you're dealing with `Some` and the compiler knows it too (because you tested
|
||||
* `isSome()` or `isNone()`) you should use `value` instead. While `Some`'s `unwrap()` and `value` will
|
||||
* both return the same value using `value` is preferable because it makes it clear that
|
||||
* there won't be an exception thrown on access.
|
||||
*
|
||||
* Throws if the value is `None`.
|
||||
*/
|
||||
unwrap(): T;
|
||||
|
||||
/**
|
||||
* Returns the contained `Some` value or a provided default.
|
||||
*
|
||||
* (This is the `unwrap_or` in rust)
|
||||
*/
|
||||
unwrapOr<T2>(val: T2): T | T2;
|
||||
|
||||
/**
|
||||
* Returns the contained `Some` value or computes a value with a provided function.
|
||||
*
|
||||
* The function is called at most one time, only if needed.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* Some('OK').unwrapOrElse(
|
||||
* () => { console.log('Called'); return 'UGH'; }
|
||||
* ) // => 'OK', nothing printed
|
||||
*
|
||||
* None.unwrapOrElse(() => 'UGH') // => 'UGH'
|
||||
* ```
|
||||
*/
|
||||
unwrapOrElse<T2>(f: () => T2): T | T2;
|
||||
|
||||
/**
|
||||
* Calls `mapper` if the Option is `Some`, otherwise returns `None`.
|
||||
* This function can be used for control flow based on `Option` values.
|
||||
*/
|
||||
andThen<T2>(mapper: (val: T) => Option<T2>): Option<T2>;
|
||||
|
||||
/**
|
||||
* Maps an `Option<T>` to `Option<U>` by applying a function to a contained `Some` value,
|
||||
* leaving a `None` value untouched.
|
||||
*
|
||||
* This function can be used to compose the Options of two functions.
|
||||
*/
|
||||
map<U>(mapper: (val: T) => U): Option<U>;
|
||||
|
||||
/**
|
||||
* Maps an `Option<T>` to `Option<U>` by either converting `T` to `U` using `mapper` (in case
|
||||
* of `Some`) or using the `default_` value (in case of `None`).
|
||||
*
|
||||
* If `default` is a result of a function call consider using `mapOrElse()` instead, it will
|
||||
* only evaluate the function when needed.
|
||||
*/
|
||||
mapOr<U>(default_: U, mapper: (val: T) => U): U;
|
||||
|
||||
/**
|
||||
* Maps an `Option<T>` to `Option<U>` by either converting `T` to `U` using `mapper` (in case
|
||||
* of `Some`) or producing a default value using the `default` function (in case of `None`).
|
||||
*/
|
||||
mapOrElse<U>(default_: () => U, mapper: (val: T) => U): U;
|
||||
|
||||
/**
|
||||
* Returns `Some()` if we have a value, otherwise returns `other`.
|
||||
*
|
||||
* `other` is evaluated eagerly. If `other` is a result of a function
|
||||
* call try `orElse()` instead – it evaluates the parameter lazily.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* Some(1).or(Some(2)) // => Some(1)
|
||||
* None.or(Some(2)) // => Some(2)
|
||||
*/
|
||||
or(other: Option<T>): Option<T>;
|
||||
|
||||
/**
|
||||
* Returns `Some()` if we have a value, otherwise returns the result
|
||||
* of calling `other()`.
|
||||
*
|
||||
* `other()` is called *only* when needed.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* Some(1).orElse(() => Some(2)) // => Some(1)
|
||||
* None.orElse(() => Some(2)) // => Some(2)
|
||||
*/
|
||||
orElse(other: () => Option<T>): Option<T>;
|
||||
|
||||
/**
|
||||
* Maps an `Option<T>` to a `Result<T, E>`.
|
||||
*/
|
||||
// toResult<E>(error: E): Result<T, E>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the None value
|
||||
*/
|
||||
class NoneImpl implements BaseOption<never> {
|
||||
isSome(): this is SomeImpl<never> {
|
||||
return false;
|
||||
}
|
||||
|
||||
isNone(): this is NoneImpl {
|
||||
return true;
|
||||
}
|
||||
|
||||
[Symbol.iterator](): Iterator<never, never, unknown> {
|
||||
return {
|
||||
next(): IteratorResult<never, never> {
|
||||
return { done: true, value: undefined! };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
unwrapOr<T2>(val: T2): T2 {
|
||||
return val;
|
||||
}
|
||||
|
||||
unwrapOrElse<T2>(f: () => T2): T2 {
|
||||
return f();
|
||||
}
|
||||
|
||||
expect(msg: string): never {
|
||||
throw new Error(`${msg}`);
|
||||
}
|
||||
|
||||
unwrap(): never {
|
||||
throw new Error(`Tried to unwrap None`);
|
||||
}
|
||||
|
||||
map(_mapper: unknown): None {
|
||||
return this;
|
||||
}
|
||||
|
||||
mapOr<T2>(default_: T2, _mapper: unknown): T2 {
|
||||
return default_;
|
||||
}
|
||||
|
||||
mapOrElse<U>(default_: () => U, _mapper: unknown): U {
|
||||
return default_();
|
||||
}
|
||||
|
||||
or<T>(other: Option<T>): Option<T> {
|
||||
return other;
|
||||
}
|
||||
|
||||
orElse<T>(other: () => Option<T>): Option<T> {
|
||||
return other();
|
||||
}
|
||||
|
||||
andThen(_op: unknown): None {
|
||||
return this;
|
||||
}
|
||||
|
||||
// toResult<E>(error: E): Err<E> {
|
||||
// return Err(error);
|
||||
// }
|
||||
|
||||
toString(): string {
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
|
||||
// Export None as a singleton, then freeze it so it can't be modified
|
||||
export const None = new NoneImpl();
|
||||
export type None = NoneImpl;
|
||||
|
||||
/**
|
||||
* Contains the success value
|
||||
*/
|
||||
class SomeImpl<T> implements BaseOption<T> {
|
||||
static readonly EMPTY = new SomeImpl<void>(undefined);
|
||||
|
||||
isSome(): this is SomeImpl<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
isNone(): this is NoneImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly value!: T;
|
||||
|
||||
[Symbol.iterator](): Iterator<T> {
|
||||
return [this.value][Symbol.iterator]();
|
||||
}
|
||||
|
||||
constructor(val: T) {
|
||||
if (!(this instanceof SomeImpl)) {
|
||||
return new SomeImpl(val);
|
||||
}
|
||||
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
unwrapOr(_val: unknown): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
unwrapOrElse(_f: unknown): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
expect(_msg: string): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
unwrap(): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
map<T2>(mapper: (val: T) => T2): Some<T2> {
|
||||
return new Some(mapper(this.value));
|
||||
}
|
||||
|
||||
mapOr<T2>(_default_: T2, mapper: (val: T) => T2): T2 {
|
||||
return mapper(this.value);
|
||||
}
|
||||
|
||||
mapOrElse<U>(_default_: () => U, mapper: (val: T) => U): U {
|
||||
return mapper(this.value);
|
||||
}
|
||||
|
||||
or(_other: Option<T>): Option<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
orElse(_other: () => Option<T>): Option<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
andThen<T2>(mapper: (val: T) => Option<T2>): Option<T2> {
|
||||
return mapper(this.value);
|
||||
}
|
||||
|
||||
// toResult<E>(_error: E): Ok<T> {
|
||||
// return Ok(this.value);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Returns the contained `Some` value, but never throws.
|
||||
* Unlike `unwrap()`, this method doesn't throw and is only callable on an Some<T>
|
||||
*
|
||||
* Therefore, it can be used instead of `unwrap()` as a maintainability safeguard
|
||||
* that will fail to compile if the type of the Option is later changed to a None that can actually occur.
|
||||
*
|
||||
* (this is the `into_Some()` in rust)
|
||||
*/
|
||||
safeUnwrap(): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Some(${toString(this.value)})`;
|
||||
}
|
||||
}
|
||||
|
||||
// This allows Some to be callable - possible because of the es5 compilation target
|
||||
// export const Some = SomeImpl as typeof SomeImpl & (<T>(val: T) => SomeImpl<T>);
|
||||
export const Some = SomeImpl;
|
||||
export type Some<T> = SomeImpl<T>;
|
||||
|
||||
export type Option<T> = Some<T> | None;
|
||||
|
||||
export type OptionSomeType<T extends Option<unknown>> =
|
||||
T extends Some<infer U> ? U : never;
|
||||
|
||||
export type OptionSomeTypes<T extends Option<unknown>[]> = {
|
||||
[key in keyof T]: T[key] extends Option<unknown>
|
||||
? OptionSomeType<T[key]>
|
||||
: never;
|
||||
};
|
||||
|
||||
export namespace Option {
|
||||
/**
|
||||
* Parse a set of `Option`s, returning an array of all `Some` values.
|
||||
* Short circuits with the first `None` found, if any
|
||||
*/
|
||||
export function all<T extends Option<any>[]>(
|
||||
...options: T
|
||||
): Option<OptionSomeTypes<T>> {
|
||||
const someOption: unknown[] = [];
|
||||
for (let option of options) {
|
||||
if (option.isSome()) {
|
||||
someOption.push(option.value);
|
||||
} else {
|
||||
return option as None;
|
||||
}
|
||||
}
|
||||
|
||||
return new Some(someOption as OptionSomeTypes<T>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a set of `Option`s, short-circuits when an input value is `Some`.
|
||||
* If no `Some` is found, returns `None`.
|
||||
*/
|
||||
export function any<T extends Option<any>[]>(
|
||||
...options: T
|
||||
): Option<OptionSomeTypes<T>[number]> {
|
||||
// short-circuits
|
||||
for (const option of options) {
|
||||
if (option.isSome()) {
|
||||
return option as Some<OptionSomeTypes<T>[number]>;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// it must be None
|
||||
return None;
|
||||
}
|
||||
|
||||
export function isOption<T = any>(value: unknown): value is Option<T> {
|
||||
return value instanceof Some || value === None;
|
||||
}
|
||||
}
|
||||
536
src/lib/thirdparty/ts-result-es/result.ts
vendored
Normal file
536
src/lib/thirdparty/ts-result-es/result.ts
vendored
Normal file
@@ -0,0 +1,536 @@
|
||||
import { toString } from "./utils";
|
||||
// import { Option, None, Some } from "./option";
|
||||
|
||||
/*
|
||||
* Missing Rust Result type methods:
|
||||
* pub fn contains<U>(&self, x: &U) -> bool
|
||||
* pub fn contains_err<F>(&self, f: &F) -> bool
|
||||
* pub fn and<U>(self, res: Result<U, E>) -> Result<U, E>
|
||||
* pub fn expect_err(self, msg: &str) -> E
|
||||
* pub fn unwrap_or_default(self) -> T
|
||||
*/
|
||||
interface BaseResult<T, E> extends Iterable<T> {
|
||||
/** `true` when the result is Ok */
|
||||
isOk(): this is OkImpl<T>;
|
||||
|
||||
/** `true` when the result is Err */
|
||||
isErr(): this is ErrImpl<E>;
|
||||
|
||||
/**
|
||||
* Returns the contained `Ok` value, if exists. Throws an error if not.
|
||||
*
|
||||
* The thrown error's
|
||||
* [`cause'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause)
|
||||
* is set to value contained in `Err`.
|
||||
*
|
||||
* If you know you're dealing with `Ok` and the compiler knows it too (because you tested
|
||||
* `isOk()` or `isErr()`) you should use `value` instead. While `Ok`'s `expect()` and `value` will
|
||||
* both return the same value using `value` is preferable because it makes it clear that
|
||||
* there won't be an exception thrown on access.
|
||||
*
|
||||
* @param msg the message to throw if no Ok value.
|
||||
*/
|
||||
expect(msg: string): T;
|
||||
|
||||
/**
|
||||
* Returns the contained `Err` value, if exists. Throws an error if not.
|
||||
* @param msg the message to throw if no Err value.
|
||||
*/
|
||||
expectErr(msg: string): E;
|
||||
|
||||
/**
|
||||
* Returns the contained `Ok` value.
|
||||
* Because this function may throw, its use is generally discouraged.
|
||||
* Instead, prefer to handle the `Err` case explicitly.
|
||||
*
|
||||
* If you know you're dealing with `Ok` and the compiler knows it too (because you tested
|
||||
* `isOk()` or `isErr()`) you should use `value` instead. While `Ok`'s `unwrap()` and `value` will
|
||||
* both return the same value using `value` is preferable because it makes it clear that
|
||||
* there won't be an exception thrown on access.
|
||||
*
|
||||
* Throws if the value is an `Err`, with a message provided by the `Err`'s value and
|
||||
* [`cause'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause)
|
||||
* set to the value.
|
||||
*/
|
||||
unwrap(): T;
|
||||
|
||||
/**
|
||||
* Returns the contained `Err` value.
|
||||
* Because this function may throw, its use is generally discouraged.
|
||||
* Instead, prefer to handle the `Ok` case explicitly and access the `error` property
|
||||
* directly.
|
||||
*
|
||||
* Throws if the value is an `Ok`, with a message provided by the `Ok`'s value and
|
||||
* [`cause'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause)
|
||||
* set to the value.
|
||||
*/
|
||||
unwrapErr(): E;
|
||||
|
||||
/**
|
||||
* Returns the contained `Ok` value or a provided default.
|
||||
*
|
||||
* @see unwrapOr
|
||||
* @deprecated in favor of unwrapOr
|
||||
*/
|
||||
else<T2>(val: T2): T | T2;
|
||||
|
||||
/**
|
||||
* Returns the contained `Ok` value or a provided default.
|
||||
*
|
||||
* (This is the `unwrap_or` in rust)
|
||||
*/
|
||||
unwrapOr<T2>(val: T2): T | T2;
|
||||
|
||||
/**
|
||||
* Returns the contained `Ok` value or computes a value with a provided function.
|
||||
*
|
||||
* The function is called at most one time, only if needed.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* Ok('OK').unwrapOrElse(
|
||||
* (error) => { console.log(`Called, got ${error}`); return 'UGH'; }
|
||||
* ) // => 'OK', nothing printed
|
||||
*
|
||||
* Err('A03B').unwrapOrElse((error) => `UGH, got ${error}`) // => 'UGH, got A03B'
|
||||
* ```
|
||||
*/
|
||||
unwrapOrElse<T2>(f: (error: E) => T2): T | T2;
|
||||
|
||||
/**
|
||||
* Calls `mapper` if the result is `Ok`, otherwise returns the `Err` value of self.
|
||||
* This function can be used for control flow based on `Result` values.
|
||||
*/
|
||||
andThen<T2, E2>(mapper: (val: T) => Result<T2, E2>): Result<T2, E | E2>;
|
||||
|
||||
/**
|
||||
* Maps a `Result<T, E>` to `Result<U, E>` by applying a function to a contained `Ok` value,
|
||||
* leaving an `Err` value untouched.
|
||||
*
|
||||
* This function can be used to compose the results of two functions.
|
||||
*/
|
||||
map<U>(mapper: (val: T) => U): Result<U, E>;
|
||||
|
||||
/**
|
||||
* Maps a `Result<T, E>` to `Result<T, F>` by applying a function to a contained `Err` value,
|
||||
* leaving an `Ok` value untouched.
|
||||
*
|
||||
* This function can be used to pass through a successful result while handling an error.
|
||||
*/
|
||||
mapErr<F>(mapper: (val: E) => F): Result<T, F>;
|
||||
|
||||
/**
|
||||
* Maps a `Result<T, E>` to `Result<U, E>` by either converting `T` to `U` using `mapper`
|
||||
* (in case of `Ok`) or using the `default_` value (in case of `Err`).
|
||||
*
|
||||
* If `default` is a result of a function call consider using `mapOrElse` instead, it will
|
||||
* only evaluate the function when needed.
|
||||
*/
|
||||
mapOr<U>(default_: U, mapper: (val: T) => U): U;
|
||||
|
||||
/**
|
||||
* Maps a `Result<T, E>` to `Result<U, E>` by either converting `T` to `U` using `mapper`
|
||||
* (in case of `Ok`) or producing a default value using the `default` function (in case of
|
||||
* `Err`).
|
||||
*/
|
||||
mapOrElse<U>(default_: (error: E) => U, mapper: (val: T) => U): U;
|
||||
|
||||
/**
|
||||
* Returns `Ok()` if we have a value, otherwise returns `other`.
|
||||
*
|
||||
* `other` is evaluated eagerly. If `other` is a result of a function
|
||||
* call try `orElse()` instead – it evaluates the parameter lazily.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* Ok(1).or(Ok(2)) // => Ok(1)
|
||||
* Err('error here').or(Ok(2)) // => Ok(2)
|
||||
*/
|
||||
or<E2>(other: Result<T, E2>): Result<T, E2>;
|
||||
|
||||
/**
|
||||
* Returns `Ok()` if we have a value, otherwise returns the result
|
||||
* of calling `other()`.
|
||||
*
|
||||
* `other()` is called *only* when needed and is passed the error value in a parameter.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* Ok(1).orElse(() => Ok(2)) // => Ok(1)
|
||||
* Err('error').orElse(() => Ok(2)) // => Ok(2)
|
||||
*/
|
||||
orElse<T2, E2>(other: (error: E) => Result<T2, E2>): Result<T | T2, E2>;
|
||||
|
||||
/**
|
||||
* Converts from `Result<T, E>` to `Option<T>`, discarding the error if any
|
||||
*
|
||||
* Similar to rust's `ok` method
|
||||
*/
|
||||
// toOption(): Option<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the error value
|
||||
*/
|
||||
export class ErrImpl<E> implements BaseResult<never, E> {
|
||||
/** An empty Err */
|
||||
static readonly EMPTY = new ErrImpl<void>(undefined);
|
||||
|
||||
isOk(): this is OkImpl<never> {
|
||||
return false;
|
||||
}
|
||||
|
||||
isErr(): this is ErrImpl<E> {
|
||||
return true;
|
||||
}
|
||||
|
||||
readonly error!: E;
|
||||
|
||||
private readonly _stack!: string;
|
||||
|
||||
[Symbol.iterator](): Iterator<never, never, unknown> {
|
||||
return {
|
||||
next(): IteratorResult<never, never> {
|
||||
return { done: true, value: undefined! };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor(val: E) {
|
||||
if (!(this instanceof ErrImpl)) {
|
||||
return new ErrImpl(val);
|
||||
}
|
||||
|
||||
this.error = val;
|
||||
|
||||
const stackLines = new Error().stack!.split("\n").slice(2);
|
||||
if (
|
||||
stackLines !== undefined &&
|
||||
stackLines.length > 0 &&
|
||||
stackLines[0].includes("ErrImpl")
|
||||
) {
|
||||
stackLines.shift();
|
||||
}
|
||||
|
||||
this._stack = stackLines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in favor of unwrapOr
|
||||
* @see unwrapOr
|
||||
*/
|
||||
else<T2>(val: T2): T2 {
|
||||
return val;
|
||||
}
|
||||
|
||||
unwrapOr<T2>(val: T2): T2 {
|
||||
return val;
|
||||
}
|
||||
|
||||
unwrapOrElse<T2>(f: (error: E) => T2): T2 {
|
||||
return f(this.error);
|
||||
}
|
||||
|
||||
expect(msg: string): never {
|
||||
// The cause casting required because of the current TS definition being overly restrictive
|
||||
// (the definition says it has to be an Error while it can be anything).
|
||||
// See https://github.com/microsoft/TypeScript/issues/45167
|
||||
throw new Error(`${msg} - Error: ${toString(this.error)}\n${this._stack}`, {
|
||||
cause: this.error as unknown,
|
||||
});
|
||||
}
|
||||
|
||||
expectErr(_msg: string): E {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
unwrap(): never {
|
||||
// The cause casting required because of the current TS definition being overly restrictive
|
||||
// (the definition says it has to be an Error while it can be anything).
|
||||
// See https://github.com/microsoft/TypeScript/issues/45167
|
||||
throw new Error(
|
||||
`Tried to unwrap Error: ${toString(this.error)}\n${this._stack}`,
|
||||
{ cause: this.error as unknown },
|
||||
);
|
||||
}
|
||||
|
||||
unwrapErr(): E {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
map(_mapper: unknown): Err<E> {
|
||||
return this;
|
||||
}
|
||||
|
||||
andThen<T2, E2>(_op: (val: never) => Result<T2, E2>): Result<T2, E | E2> {
|
||||
return this;
|
||||
}
|
||||
|
||||
mapErr<E2>(mapper: (err: E) => E2): Err<E2> {
|
||||
return new Err(mapper(this.error));
|
||||
}
|
||||
|
||||
mapOr<U>(default_: U, _mapper: unknown): U {
|
||||
return default_;
|
||||
}
|
||||
|
||||
mapOrElse<U>(default_: (error: E) => U, _mapper: unknown): U {
|
||||
return default_(this.error);
|
||||
}
|
||||
|
||||
or<T>(other: Ok<T>): Result<T, never>;
|
||||
or<R extends Result<unknown, unknown>>(other: R): R;
|
||||
or<T, E2>(other: Result<T, E2>): Result<T, E2> {
|
||||
return other;
|
||||
}
|
||||
|
||||
orElse<T2, E2>(other: (error: E) => Result<T2, E2>): Result<T2, E2> {
|
||||
return other(this.error);
|
||||
}
|
||||
|
||||
// toOption(): Option<never> {
|
||||
// return None;
|
||||
// }
|
||||
|
||||
toString(): string {
|
||||
return `Err(${toString(this.error)})`;
|
||||
}
|
||||
|
||||
get stack(): string | undefined {
|
||||
return `${this.toString()}\n${this._stack}`;
|
||||
}
|
||||
}
|
||||
|
||||
// This allows Err to be callable - possible because of the es5 compilation target
|
||||
// export const Err = ErrImpl as typeof ErrImpl & (<E>(err: E) => Err<E>);
|
||||
export const Err = ErrImpl;
|
||||
export type Err<E> = ErrImpl<E>;
|
||||
|
||||
/**
|
||||
* Contains the success value
|
||||
*/
|
||||
export class OkImpl<T> implements BaseResult<T, never> {
|
||||
static readonly EMPTY = new OkImpl<void>(undefined);
|
||||
|
||||
isOk(): this is OkImpl<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
isErr(): this is ErrImpl<never> {
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly value!: T;
|
||||
|
||||
[Symbol.iterator](): Iterator<T> {
|
||||
return [this.value][Symbol.iterator]();
|
||||
}
|
||||
|
||||
constructor(val: T) {
|
||||
if (!(this instanceof OkImpl)) {
|
||||
return new OkImpl(val);
|
||||
}
|
||||
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see unwrapOr
|
||||
* @deprecated in favor of unwrapOr
|
||||
*/
|
||||
else(_val: unknown): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
unwrapOr(_val: unknown): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
unwrapOrElse(_f: unknown): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
expect(_msg: string): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
expectErr(msg: string): never {
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
unwrap(): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
unwrapErr(): never {
|
||||
// The cause casting required because of the current TS definition being overly restrictive
|
||||
// (the definition says it has to be an Error while it can be anything).
|
||||
// See https://github.com/microsoft/TypeScript/issues/45167
|
||||
throw new Error(`Tried to unwrap Ok: ${toString(this.value)}`, {
|
||||
cause: this.value as unknown,
|
||||
});
|
||||
}
|
||||
|
||||
map<T2>(mapper: (val: T) => T2): Ok<T2> {
|
||||
return new Ok(mapper(this.value));
|
||||
}
|
||||
|
||||
andThen<T2, E2>(mapper: (val: T) => Result<T2, E2>): Result<T2, E2> {
|
||||
return mapper(this.value);
|
||||
}
|
||||
|
||||
mapErr(_mapper: unknown): Ok<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
mapOr<U>(_default_: U, mapper: (val: T) => U): U {
|
||||
return mapper(this.value);
|
||||
}
|
||||
|
||||
mapOrElse<U>(_default_: (_error: never) => U, mapper: (val: T) => U): U {
|
||||
return mapper(this.value);
|
||||
}
|
||||
|
||||
or(_other: Result<T, unknown>): Ok<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
orElse<T2, E2>(_other: (error: never) => Result<T2, E2>): Result<T, never> {
|
||||
return this;
|
||||
}
|
||||
|
||||
// toOption(): Option<T> {
|
||||
// return Some(this.value);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Returns the contained `Ok` value, but never throws.
|
||||
* Unlike `unwrap()`, this method doesn't throw and is only callable on an Ok<T>
|
||||
*
|
||||
* Therefore, it can be used instead of `unwrap()` as a maintainability safeguard
|
||||
* that will fail to compile if the error type of the Result is later changed to an error that can actually occur.
|
||||
*
|
||||
* (this is the `into_ok()` in rust)
|
||||
*/
|
||||
safeUnwrap(): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Ok(${toString(this.value)})`;
|
||||
}
|
||||
}
|
||||
|
||||
// This allows Ok to be callable - possible because of the es5 compilation target
|
||||
// export const Ok = OkImpl as typeof OkImpl & (<T>(val: T) => Ok<T>);
|
||||
export const Ok = OkImpl;
|
||||
export type Ok<T> = OkImpl<T>;
|
||||
|
||||
export type Result<T, E = Error> = Ok<T> | Err<E>;
|
||||
|
||||
export type ResultOkType<T extends Result<unknown, unknown>> =
|
||||
T extends Ok<infer U> ? U : never;
|
||||
export type ResultErrType<T> = T extends Err<infer U> ? U : never;
|
||||
|
||||
export type ResultOkTypes<T extends Result<unknown, unknown>[]> = {
|
||||
[key in keyof T]: T[key] extends Result<infer _U, unknown>
|
||||
? ResultOkType<T[key]>
|
||||
: never;
|
||||
};
|
||||
export type ResultErrTypes<T extends Result<unknown, unknown>[]> = {
|
||||
[key in keyof T]: T[key] extends Result<infer _U, unknown>
|
||||
? ResultErrType<T[key]>
|
||||
: never;
|
||||
};
|
||||
|
||||
export namespace Result {
|
||||
/**
|
||||
* Parse a set of `Result`s, returning an array of all `Ok` values.
|
||||
* Short circuits with the first `Err` found, if any
|
||||
*/
|
||||
export function all<const T extends Result<any, any>[]>(
|
||||
results: T,
|
||||
): Result<ResultOkTypes<T>, ResultErrTypes<T>[number]> {
|
||||
const okResult: unknown[] = [];
|
||||
for (let result of results) {
|
||||
if (result.isOk()) {
|
||||
okResult.push(result.value);
|
||||
} else {
|
||||
return result as Err<ResultErrTypes<T>[number]>;
|
||||
}
|
||||
}
|
||||
|
||||
return new Ok(okResult as ResultOkTypes<T>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a set of `Result`s, short-circuits when an input value is `Ok`.
|
||||
* If no `Ok` is found, returns an `Err` containing the collected error values
|
||||
*/
|
||||
export function any<const T extends Result<any, any>[]>(
|
||||
results: T,
|
||||
): Result<ResultOkTypes<T>[number], ResultErrTypes<T>> {
|
||||
const errResult: unknown[] = [];
|
||||
|
||||
// short-circuits
|
||||
for (const result of results) {
|
||||
if (result.isOk()) {
|
||||
return result as Ok<ResultOkTypes<T>[number]>;
|
||||
} else {
|
||||
errResult.push(result.error);
|
||||
}
|
||||
}
|
||||
|
||||
// it must be a Err
|
||||
return new Err(errResult as ResultErrTypes<T>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an operation that may throw an Error (`try-catch` style) into checked exception style
|
||||
* @param op The operation function
|
||||
*/
|
||||
export function wrap<T, E = unknown>(op: () => T): Result<T, E> {
|
||||
try {
|
||||
return new Ok(op());
|
||||
} catch (e) {
|
||||
return new Err<E>(e as E);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an async operation that may throw an Error (`try-catch` style) into checked exception style
|
||||
* @param op The operation function
|
||||
*/
|
||||
export function wrapAsync<T, E = unknown>(
|
||||
op: () => Promise<T>,
|
||||
): Promise<Result<T, E>> {
|
||||
try {
|
||||
return op()
|
||||
.then((val) => new Ok(val))
|
||||
.catch((e) => new Err(e));
|
||||
} catch (e) {
|
||||
return Promise.resolve(new Err(e as E));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Partitions a set of results, separating the `Ok` and `Err` values.
|
||||
*/
|
||||
export function partition<T extends Result<any, any>[]>(
|
||||
results: T,
|
||||
): [ResultOkTypes<T>, ResultErrTypes<T>] {
|
||||
return results.reduce(
|
||||
([oks, errors], v) =>
|
||||
v.isOk()
|
||||
? [[...oks, v.value] as ResultOkTypes<T>, errors]
|
||||
: [oks, [...errors, v.error] as ResultErrTypes<T>],
|
||||
[[], []] as [ResultOkTypes<T>, ResultErrTypes<T>],
|
||||
);
|
||||
}
|
||||
|
||||
export function isResult<T = any, E = any>(
|
||||
val: unknown,
|
||||
): val is Result<T, E> {
|
||||
return val instanceof Err || val instanceof Ok;
|
||||
}
|
||||
}
|
||||
11
src/lib/thirdparty/ts-result-es/utils.ts
vendored
Normal file
11
src/lib/thirdparty/ts-result-es/utils.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
export function toString(val: unknown): string {
|
||||
let value = String(val);
|
||||
if (value === "[object Object]") {
|
||||
try {
|
||||
value = textutils.serialize(val as object);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
Reference in New Issue
Block a user