mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-12-20 13:37:49 +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 { launchAccessControlTUI } from "./tui";
|
||||||
import * as peripheralManager from "../lib/PeripheralManager";
|
import * as peripheralManager from "../lib/PeripheralManager";
|
||||||
import { deepCopy } from "@/lib/common";
|
import { deepCopy } from "@/lib/common";
|
||||||
import { ReadWriteLock } from "@/lib/ReadWriteLock";
|
import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock";
|
||||||
|
|
||||||
const args = [...$vararg];
|
const args = [...$vararg];
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +1,62 @@
|
|||||||
import { CraftManager } from "@/lib/CraftManager";
|
import { CraftManager } from "@/lib/CraftManager";
|
||||||
import * as peripheralManager from "../lib/PeripheralManager";
|
|
||||||
import { CCLog } from "@/lib/ccLog";
|
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",
|
packagesContainer: "minecraft:chest_10",
|
||||||
itemsContainer: "minecraft:chest_9",
|
itemsContainer: "minecraft:chest_9",
|
||||||
packageExtractor: "create:packager_1",
|
packageExtractor: "create:packager_1",
|
||||||
blockReader: "front",
|
blockReader: "front",
|
||||||
wiredModem: "back",
|
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() {
|
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);
|
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) {
|
while (true) {
|
||||||
if (!hasPackage) os.pullEvent("redstone");
|
if (!hasPackage) os.pullEvent("redstone");
|
||||||
hasPackage = redstone.getInput("front");
|
hasPackage = redstone.getInput(peripheralsNames.redstone);
|
||||||
if (!hasPackage) {
|
if (!hasPackage) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
log.info(`Package detected`);
|
logger.info(`Package detected`);
|
||||||
|
|
||||||
const itemsInfo = packagesContainer.list();
|
const itemsInfo = packagesContainer.list();
|
||||||
for (const key in itemsInfo) {
|
for (const key in itemsInfo) {
|
||||||
const slot = parseInt(key);
|
const slot = parseInt(key);
|
||||||
const item = itemsInfo[slot];
|
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
|
// Get package NBT
|
||||||
packagesContainer.pushItems(turtleLocalName, slot);
|
packagesContainer.pushItems(turtleLocalName, slot);
|
||||||
@@ -65,9 +67,9 @@ function main() {
|
|||||||
const packageRecipes = CraftManager.getPackageRecipe(packageInfo);
|
const packageRecipes = CraftManager.getPackageRecipe(packageInfo);
|
||||||
|
|
||||||
// No recipe, just extract package
|
// No recipe, just extract package
|
||||||
if (packageRecipes == undefined) {
|
if (packageRecipes.isNone()) {
|
||||||
packageExtractor.pullItems(turtleLocalName, 1);
|
packageExtractor.pullItems(turtleLocalName, 1);
|
||||||
log.info(`No recipe, just pass`);
|
logger.info(`No recipe, just pass`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ function main() {
|
|||||||
packageExtractor.pullItems(turtleLocalName, 1);
|
packageExtractor.pullItems(turtleLocalName, 1);
|
||||||
|
|
||||||
// Pull and craft multi recipe
|
// Pull and craft multi recipe
|
||||||
for (const recipe of packageRecipes) {
|
for (const recipe of packageRecipes.value) {
|
||||||
let craftOutputItem: BlockItemDetailData | undefined = undefined;
|
let craftOutputItem: BlockItemDetailData | undefined = undefined;
|
||||||
let restCraftCnt = recipe.Count;
|
let restCraftCnt = recipe.Count;
|
||||||
|
|
||||||
@@ -84,15 +86,17 @@ function main() {
|
|||||||
// Clear workbench
|
// Clear workbench
|
||||||
craftManager.pushAll(itemsContainer);
|
craftManager.pushAll(itemsContainer);
|
||||||
|
|
||||||
log.info(`Pull items according to a recipe`);
|
logger.info(`Pull items according to a recipe`);
|
||||||
const craftCnt = craftManager.pullItems(
|
const craftCnt = craftManager
|
||||||
recipe,
|
.pullItems(recipe, itemsContainer, restCraftCnt)
|
||||||
itemsContainer,
|
.unwrapOrElse((error) => {
|
||||||
restCraftCnt,
|
logger.error(error.message);
|
||||||
);
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
if (craftCnt == 0) break;
|
if (craftCnt == 0) break;
|
||||||
craftManager.craft();
|
craftManager.craft();
|
||||||
log.info(`Craft ${craftCnt} times`);
|
logger.info(`Craft ${craftCnt} times`);
|
||||||
restCraftCnt -= craftCnt;
|
restCraftCnt -= craftCnt;
|
||||||
|
|
||||||
// Get output item
|
// Get output item
|
||||||
@@ -101,9 +105,9 @@ function main() {
|
|||||||
|
|
||||||
// Finally output
|
// Finally output
|
||||||
if (restCraftCnt > 0) {
|
if (restCraftCnt > 0) {
|
||||||
log.warn(`Only craft ${recipe.Count - restCraftCnt} times`);
|
logger.warn(`Only craft ${recipe.Count - restCraftCnt} times`);
|
||||||
} else {
|
} else {
|
||||||
log.info(`Finish craft ${recipe.Count}x ${craftOutputItem?.id}`);
|
logger.info(`Finish craft ${recipe.Count}x ${craftOutputItem?.id}`);
|
||||||
}
|
}
|
||||||
craftManager.pushAll(itemsContainer);
|
craftManager.pushAll(itemsContainer);
|
||||||
}
|
}
|
||||||
@@ -114,7 +118,7 @@ function main() {
|
|||||||
try {
|
try {
|
||||||
main();
|
main();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
log.error(textutils.serialise(error as object));
|
logger.error(textutils.serialise(error as object));
|
||||||
} finally {
|
} finally {
|
||||||
log.close();
|
logger.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { CCLog } from "./ccLog";
|
import { Queue } from "./datatype/Queue";
|
||||||
|
import { Result, Ok, Err, Option, Some, None } from "./thirdparty/ts-result-es";
|
||||||
const log = new CCLog("CraftManager.log");
|
|
||||||
|
|
||||||
// ComputerCraft Turtle inventory layout:
|
// ComputerCraft Turtle inventory layout:
|
||||||
// 1, 2, 3, 4
|
// 1, 2, 3, 4
|
||||||
@@ -70,6 +69,13 @@ interface CraftRecipe {
|
|||||||
Count: number;
|
Count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface InventorySlotInfo {
|
||||||
|
name: string;
|
||||||
|
count: number;
|
||||||
|
maxCount: number;
|
||||||
|
slotNum: number;
|
||||||
|
}
|
||||||
|
|
||||||
type CraftMode = "keep" | "keepProduct" | "keepIngredient";
|
type CraftMode = "keep" | "keepProduct" | "keepIngredient";
|
||||||
|
|
||||||
class CraftManager {
|
class CraftManager {
|
||||||
@@ -117,80 +123,122 @@ class CraftManager {
|
|||||||
|
|
||||||
public static getPackageRecipe(
|
public static getPackageRecipe(
|
||||||
item: BlockItemDetailData,
|
item: BlockItemDetailData,
|
||||||
): CraftRecipe[] | undefined {
|
): Option<CraftRecipe[]> {
|
||||||
if (
|
if (
|
||||||
!item.id.includes("create:cardboard_package") ||
|
!item.id.includes("create:cardboard_package") ||
|
||||||
(item.tag as CreatePackageTag)?.Fragment?.OrderContext
|
(item.tag as CreatePackageTag)?.Fragment?.OrderContext
|
||||||
?.OrderedCrafts?.[0] == undefined
|
?.OrderedCrafts?.[0] == undefined
|
||||||
) {
|
) {
|
||||||
return undefined;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderedCraft = (item.tag as CreatePackageTag).Fragment.OrderContext
|
const orderedCraft = (item.tag as CreatePackageTag).Fragment.OrderContext
|
||||||
.OrderedCrafts;
|
.OrderedCrafts;
|
||||||
return orderedCraft.map((value, _) => ({
|
return new Some(
|
||||||
PatternEntries: value.Pattern.Entries,
|
orderedCraft.map((value, _) => ({
|
||||||
Count: value.Count,
|
PatternEntries: value.Pattern.Entries,
|
||||||
}));
|
Count: value.Count,
|
||||||
|
})),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public pullItems(
|
public pullItems(
|
||||||
recipe: CraftRecipe,
|
recipe: CraftRecipe,
|
||||||
inventory: InventoryPeripheral,
|
srcInventory: InventoryPeripheral,
|
||||||
limit: number,
|
craftCnt: number,
|
||||||
): number {
|
): Result<number> {
|
||||||
let maxCraftCount = limit;
|
// 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) {
|
for (const index in recipe.PatternEntries) {
|
||||||
const entry = recipe.PatternEntries[index];
|
const entry = recipe.PatternEntries[index];
|
||||||
if (entry.Item.Count == 0 || entry.Item.id == "minecraft:air") {
|
if (entry.Item.Count == 0 || entry.Item.id == "minecraft:air") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ingredientList = inventory.list();
|
if (!ingredientMap.has(entry.Item.id))
|
||||||
let restCount = maxCraftCount;
|
return new Err(Error(`No ingredient match ${entry.Item.id}`));
|
||||||
for (const key in ingredientList) {
|
|
||||||
// Get item detail and check max count
|
const ingredient = ingredientMap.get(entry.Item.id)!;
|
||||||
const slot = parseInt(key);
|
let restCraftCnt = maxCraftCnt;
|
||||||
const ingredient = inventory.getItemDetail(slot)!;
|
while (restCraftCnt > 0 && ingredient.size() > 0) {
|
||||||
if (entry.Item.id != ingredient.name) {
|
const slotItem = ingredient.dequeue()!;
|
||||||
continue;
|
|
||||||
|
// Check item max stack count
|
||||||
|
if (slotItem.maxCount < maxCraftCnt) {
|
||||||
|
maxCraftCnt = slotItem.maxCount;
|
||||||
|
restCraftCnt = maxCraftCnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ingredientMaxCount = ingredient.maxCount;
|
if (slotItem.count >= restCraftCnt) {
|
||||||
if (maxCraftCount > ingredientMaxCount) {
|
const pushItemsCnt = srcInventory.pushItems(
|
||||||
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(
|
|
||||||
this.localName,
|
this.localName,
|
||||||
slot,
|
slotItem.slotNum,
|
||||||
restCount,
|
restCraftCnt,
|
||||||
CRAFT_SLOT_TABLE[parseInt(index) - 1],
|
CRAFT_SLOT_TABLE[index],
|
||||||
);
|
);
|
||||||
restCount = 0;
|
if (pushItemsCnt !== restCraftCnt)
|
||||||
break;
|
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 {
|
} else {
|
||||||
inventory.pushItems(
|
const pushItemsCnt = srcInventory.pushItems(
|
||||||
this.localName,
|
this.localName,
|
||||||
slot,
|
slotItem.slotNum,
|
||||||
ingredient.count,
|
slotItem.count,
|
||||||
CRAFT_SLOT_TABLE[parseInt(index) - 1],
|
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 Source project, ts-deepcopy https://github.com/ykdr2017/ts-deepcopy
|
||||||
* @see Code pen https://codepen.io/erikvullings/pen/ejyBYg
|
* @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) {
|
if (target === null) {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
@@ -48,4 +48,4 @@ export const deepCopy = <T>(target: T): T => {
|
|||||||
return cp as T;
|
return cp as T;
|
||||||
}
|
}
|
||||||
return target;
|
return target;
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export class Node<T> {
|
export class Node<T> {
|
||||||
public value: T;
|
public value: T;
|
||||||
public next?: Node<T>
|
public next?: Node<T>;
|
||||||
public prev?: Node<T>
|
public prev?: Node<T>;
|
||||||
|
|
||||||
constructor(value: T, next?: Node<T>, prev?: Node<T>) {
|
constructor(value: T, next?: Node<T>, prev?: Node<T>) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@@ -15,10 +15,15 @@ export class Queue<T> {
|
|||||||
private _tail?: Node<T>;
|
private _tail?: Node<T>;
|
||||||
private _size: number;
|
private _size: number;
|
||||||
|
|
||||||
constructor() {
|
constructor(data?: T[]) {
|
||||||
this._head = undefined;
|
this._head = undefined;
|
||||||
this._tail = undefined;
|
this._tail = undefined;
|
||||||
this._size = 0;
|
this._size = 0;
|
||||||
|
|
||||||
|
if (data === undefined) return;
|
||||||
|
for (const item of data) {
|
||||||
|
this.enqueue(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enqueue(data: T): void {
|
public enqueue(data: T): void {
|
||||||
@@ -37,11 +42,11 @@ export class Queue<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public dequeue(): T | undefined {
|
public dequeue(): T | undefined {
|
||||||
|
if (this._head === undefined) return undefined;
|
||||||
const node = this._head;
|
const node = this._head;
|
||||||
if (node === undefined) return undefined;
|
|
||||||
|
|
||||||
this._head = this._head!.next;
|
this._head = node.next;
|
||||||
this._head!.prev = undefined;
|
if (this._head !== undefined) this._head.prev = undefined;
|
||||||
this._size--;
|
this._size--;
|
||||||
|
|
||||||
return node.value;
|
return node.value;
|
||||||
@@ -68,8 +73,7 @@ export class Queue<T> {
|
|||||||
const array: T[] = [];
|
const array: T[] = [];
|
||||||
let currentNode: Node<T> = this._head!;
|
let currentNode: Node<T> = this._head!;
|
||||||
for (let i = 0; i < this._size; i++) {
|
for (let i = 0; i < this._size; i++) {
|
||||||
if (currentNode.value !== undefined)
|
if (currentNode.value !== undefined) array.push(currentNode.value);
|
||||||
array.push(currentNode.value);
|
|
||||||
|
|
||||||
currentNode = currentNode.next!;
|
currentNode = currentNode.next!;
|
||||||
}
|
}
|
||||||
@@ -82,13 +86,13 @@ export class Queue<T> {
|
|||||||
return {
|
return {
|
||||||
next(): IteratorResult<T> {
|
next(): IteratorResult<T> {
|
||||||
if (currentNode === undefined) {
|
if (currentNode === undefined) {
|
||||||
return { value: undefined, done: true }
|
return { value: undefined, done: true };
|
||||||
} else {
|
} else {
|
||||||
const data = currentNode.value;
|
const data = currentNode.value;
|
||||||
currentNode = currentNode.next;
|
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_CANCELED = new Error("Request canceled");
|
||||||
// const E_INSUFFICIENT_RESOURCES = new Error("Insufficient resources");
|
// 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