@ugo-code/streamline.js
v1.0.6
Published
A utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript
Maintainers
Readme
@ugo-code/streamline.js
A lightweight, powerful, and zero-dependency utility library for TypeScript and JavaScript that simplifies complex asynchronous operations, enhances array manipulations, and provides robust cryptography tools.
Overview
@ugo-code/streamline.js provides a set of straight-forward, robust, and efficient functions designed to tackle common challenges in modern web development. Whether you need to prevent redundant API calls, handle transient network errors gracefully, perform complex data analysis on arrays, or secure your data with modern cryptography, this library has you covered.
Built with TypeScript, it offers full type safety and is designed for seamless integration into any project, supporting both ES Modules and CommonJS.
Features
- Robust Async Control:
singleExecution: Guarantees that an async function is only executed once at a time for a given key, preventing race conditions and redundant operations.retry: Automatically retries a failing async task with a configurable exponential backoff strategy, perfect for handling unreliable network requests.
- Powerful Array Utilities:
ArraySL: An extendedArrayclass that supercharges your data manipulations with convenient getters and powerful methods.
- Secure Cryptography:
encryptString/decryptString: Encrypt and decrypt strings using AES-256-GCM with a time-to-live (TTL) to prevent replay attacks.hashObject: Create a deterministic SHA-256 hash of any JavaScript value, including complex nested objects, Maps, Sets, and even structures with circular references.
- Type-Safe: Fully written in TypeScript to provide excellent autocompletion and catch errors at compile time.
- Seamless Chaining:
ArraySLmethods (including native ones like.mapand.filter) return anArraySLinstance, allowing for elegant and readable method chaining. - Lightweight & Zero-Dependency: Keeps your
node_modulesfolder clean and your bundle size small.
Installation
npm install @ugo-code/streamline.jsor
yarn add @ugo-code/streamline.jsor
bun add @ugo-code/streamline.jsAPI Documentation
A quick look at the utilities this package provides. Click on any utility to see its details.
singleExecution<TResult>(taskFn, [key])
Ensures that an asynchronous task is only executed once at a time for a given unique key. If called again with the same key while the task is running, it returns the promise of the existing task.
taskFn:() => Promise<TResult>- The asynchronous function to execute.key(optional):SerializableKey- A unique identifier for the task. Can be a string, number, object, or array. If not provided, a key is generated by hashing the function's source code.
import { singleExecution } from "@ugo-code/streamline.js/singleExecution";
async function fetchUser(userId: string) {
return singleExecution(
() => {
console.log(`Fetching user ${userId}...`);
// Imagine this is an API call
return new Promise((resolve) =>
setTimeout(() => resolve({ id: userId, name: "John Doe" }), 100)
);
},
`user-${userId}` // Unique key for this user
);
}
// Both calls will trigger only one "Fetching user 123..." log
Promise.all([fetchUser("123"), fetchUser("123")]);retry<TResult>(taskFn, [options])
Executes an asynchronous task and automatically retries it with an exponential backoff strategy if it fails.
taskFn:() => Promise<TResult>- The asynchronous function to execute.options(optional):RetryOptionslimit:number(default:2) - The maximum number of retry attempts.initialDelay:number(default:0) - The initial delay in milliseconds before the first retry.maxDelay:number(default:Infinity) - The maximum delay between retries.onRetry:(error, attempt, delay) => void- A callback executed before each retry.
import { retry } from "@ugo-code/streamline.js/retry";
let attempt = 0;
async function fetchUnreliableData() {
attempt++;
console.log(`Attempt #${attempt}...`);
if (attempt < 3) {
throw new Error("Network error");
}
return { data: "Finally!" };
}
const data = await retry(fetchUnreliableData, {
limit: 3,
initialDelay: 100,
onRetry: (error, attempt) => {
console.log(`Attempt ${attempt} failed. Retrying...`);
},
});
console.log(data); // { data: 'Finally!' }ArraySL<T>
An extended Array class with convenient getters and powerful utility methods.
import { ArraySL } from "@ugo-code/streamline.js/array";
const numbers = new ArraySL([1, 2, 3, 4, 5]);Getters
.first: Returns the first element..last: Returns the last element..random: Returns a random element.
const arr = new ArraySL([10, 20, 30]);
console.log(arr.first); // 10
console.log(arr.last); // 30Methods
.duplicates([options]): Returns a newArraySLcontaining duplicate elements, with configurable modes (all,first,subsequent) and an optionalaccessor..middle(): Returns the middle item(s) of the array based on their index, not their value. This is not a median calculation..mostFrequent([accessor]): Finds the most frequently occurring item(s), using an optionalaccessorfunction..unique([options]): Returns a newArraySLwith unique elements based on an optionalaccessorfunction.
import { ArraySL } from "@ugo-code/streamline.js/array";
const products = new ArraySL([
{ category: "A", price: 10 },
{ category: "B", price: 20 },
{ category: "A", price: 30 },
{ category: "C", price: 20 },
]);
// Get the first product from the 'A' category after sorting by price
const result = products
.filter((p) => p.category === "A")
.sort((a, b) => a.price - b.price).first;
console.log(result); // { category: 'A', price: 10 }Cryptography
Provides a set of functions for secure data handling using the Web Crypto API.
encryptString(plaintext, secretKey, [options])
Encrypts a string using AES-256-GCM and embeds a Time-To-Live (TTL).
plaintext:string- The string to encrypt.secretKey:string- The secret key for encryption.options(optional):{ ttl?: number | null; pbkdf2Iterations?: number }- An options object.ttl: (default:3600000ms, 1 hour) - The validity period in milliseconds. Passnullto disable expiration.pbkdf2Iterations:number(default:100000) - The number of iterations for key derivation (PBKDF2). Warning: Reducing this value weakens security.
- Throws:
CryptoErrorif the environment is unsupported or encryption fails.
import { encryptString } from "@ugo-code/streamline.js/crypto";
const secret = "my-super-secret-key";
// Encrypt with a 5-second TTL
const encrypted = await encryptString("Hello, World!", secret, { ttl: 5000 });
// Encrypt without an expiration
const permanent = await encryptString("This will not expire", secret, {
ttl: null,
});decryptString(encryptedData, secretKey)
Decrypts a string encrypted with encryptString, verifying its TTL.
encryptedData:string- The Base64 encoded string fromencryptString.secretKey:string- The same secret key used for encryption.- Throws:
CryptoErrorwith specific codes for different failure reasons.
import { decryptString } from "@ugo-code/streamline.js/crypto";
// Assuming 'encrypted' is from the previous example
const decrypted = await decryptString(encrypted, secret);
console.log(decrypted); // "Hello, World!"
// After 5 seconds, this would throw a CryptoError.hashObject(obj)
Calculates a deterministic SHA-256 hash of any JavaScript value.
This function creates a consistent, canonical string representation of any object before hashing. It correctly handles complex and nested data structures, including:
- Objects with keys in any order.
- Arrays,
Map, andSetobjects (with elements in any order). - Primitives and special values like
Date,RegExp,BigInt,Symbol, andundefined. - Objects with circular references (without throwing an error).
This ensures that the same logical object always produces the same hash.
obj:any- The value to hash.
import { hashObject } from "@ugo-code/streamline.js/crypto";
// Works with complex, nested objects
const obj1 = {
b: { d: new Set([1, new Date(0)]), c: 3 },
a: 2,
};
const obj2 = {
a: 2,
b: { c: 3, d: new Set([new Date(0), 1]) },
};
const hash1 = await hashObject(obj1);
const hash2 = await hashObject(obj2);
console.log(hash1 === hash2); // true
// Handles circular references
const circular: any = { key: "value" };
circular.self = circular;
const circularHash = await hashObject(circular);
console.log(circularHash); // Produces a consistent hash without crashingError Handling
The cryptography functions throw a CryptoError for specific, catchable failures.
CryptoError.code: A machine-readable error code.UNSUPPORTED_ENVIRONMENT: The Web Crypto API is not available.ENCRYPTION_FAILED: The encryption process failed.DECRYPTION_FAILED: Decryption failed, likely due to a wrong key or tampered data.INVALID_DATA: The encrypted payload is malformed.EXPIRED: The data's TTL has passed.
import { decryptString, CryptoError } from "@ugo-code/streamline.js/crypto";
try {
const decrypted = await decryptString(expiredData, secret);
} catch (error) {
if (error instanceof CryptoError && error.code === "EXPIRED") {
console.error("The data has expired!");
} else {
console.error("An unexpected error occurred:", error);
}
}Contributing
Contributions are welcome! Please feel free to submit a pull request or open an issue.
- Fork the repository.
- Create your feature branch (
git checkout -b feature/AmazingFeature). - Commit your changes (
git commit -m 'Add some AmazingFeature'). - Push to the branch (
git push origin feature/AmazingFeature). - Open a pull request.
License
This project is licensed under the MIT License. See the LICENSE file for details.
