Files
xo-stack/packages/primitives/src/utils.ts
T

169 lines
4.1 KiB
TypeScript

import { Transaction } from './transaction.js';
import { binToHex } from '@bitauth/libauth';
/**
* Sort transactions by the order in which they should be broadcast.
*/
export function sortTransactions(
transactions: Array<Transaction>,
): Array<Transaction> {
const transactionMap = transactions.reduce(
(map, transaction) => {
const txHash = transaction.toHash().toHex();
map[txHash] = transaction;
return map;
},
{} as Record<string, Transaction>,
);
const visited = new Set<string>();
const order: Transaction[] = [];
const visit = (txid: string) => {
if (visited.has(txid)) return;
const tx = transactionMap[txid];
if (!tx) return;
visited.add(txid);
// First, visit all transactions that this transaction depends on
for (const input of tx.getInputs()) {
const outpointTransactionHashHex = binToHex(
input.outpointTransactionHash,
);
visit(outpointTransactionHashHex);
}
order.push(tx);
};
// Start the topological sort from each transaction
for (const tx of Object.values(transactionMap)) {
visit(tx.toHash().toHex());
}
return order;
}
export class ExtMap<T> extends Map<PropertyKey, T> {
/**
* Creates a new ExtMap from an array using a key selector
*/
static fromArray<T, K extends PropertyKey>(
array: readonly T[],
keySelector: (item: T) => K,
): ExtMap<T> {
const collection = new ExtMap<T>();
array.forEach((item) => {
const key = keySelector(item);
collection.set(key, item);
});
return collection;
}
static fromObject<T>(object: { [key: PropertyKey]: T }): ExtMap<T> {
const collection = new ExtMap<T>();
for (let [key, value] of Object.entries(object)) {
collection.set(key, value);
}
return collection;
}
/**
* Flattens an array of ExtMaps into a single ExtMap
* Last collection's values take precedence for duplicate keys
*/
static flatten<T>(collections: ExtMap<T>[]): ExtMap<T> {
const flattened = new ExtMap<T>();
collections.forEach((collection) => {
for (const [key, value] of collection.entries()) {
flattened.set(key, value);
}
});
return flattened;
}
getOrFail(key: string): T {
const value = this.get(key);
if (!value) {
throw new Error(`Failed to find key "${key}"`);
}
return value;
}
/**
* Returns a new ExtMap with filtered entries
*/
filter(predicate: (value: T, key: PropertyKey) => boolean): ExtMap<T> {
const filtered = new ExtMap<T>();
for (const [key, value] of this.entries()) {
if (predicate(value, key)) {
filtered.set(key, value);
}
}
return filtered;
}
/**
* Returns a new ExtMap with mapped values
*/
map<U>(mapper: (value: T, key: PropertyKey) => U): ExtMap<U> {
const mapped = new ExtMap<U>();
for (const [key, value] of this.entries()) {
mapped.set(key, mapper(value, key));
}
return mapped;
}
/**
* Returns a new ExtMap with entries sorted by the given comparator
* @param comparator - Function to determine the sort order
* @returns A new ExtMap with sorted entries
*/
sort(comparator: (a: T, b: T) => number): ExtMap<T> {
const entries = Array.from(this.entries());
const sorted = new ExtMap<T>();
entries
.sort(([, valueA], [, valueB]) => comparator(valueA, valueB))
.forEach(([key, value]) => {
sorted.set(key, value);
});
return sorted;
}
/**
* Converts the collection to a plain object
*/
toObject(): Record<PropertyKey, T> {
return Object.fromEntries(this.entries());
}
/**
* Converts the collection to an array of values
*/
toArray(): T[] {
return Array.from(this.values());
}
/**
* Converts the collection to a Set
* @param callback - Optional function to transform keys before adding to Set
* @returns A Set containing either the keys or transformed values
*/
toSet<U = PropertyKey>(callback?: (key: PropertyKey, value: T) => U): Set<U> {
const set = new Set<U>();
for (const [key, value] of this.entries()) {
set.add(callback ? callback(key, value) : (key as U));
}
return set;
}
}