UNPKG

@ugo-code/streamline.js

Version:

A utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript

342 lines (242 loc) 11.2 kB
# @ugo-code/streamline.js [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) [![npm version](https://badge.fury.io/js/%40ugo-code%2Fstreamline.js.svg)](https://badge.fury.io/js/%40ugo-code%2Fstreamline.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 extended `Array` class 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**: `ArraySL` methods (including native ones like `.map` and `.filter`) return an `ArraySL` instance, allowing for elegant and readable method chaining. - **Lightweight & Zero-Dependency**: Keeps your `node_modules` folder clean and your bundle size small. ## Installation ```bash npm install @ugo-code/streamline.js ``` or ```bash yarn add @ugo-code/streamline.js ``` or ```bash bun add @ugo-code/streamline.js ``` ## API Documentation A quick look at the utilities this package provides. Click on any utility to see its details. - [**`singleExecution`**](#singleExecutiontresulttaskfn-key) - [**`retry`**](#retrytresulttaskfn-options) - [**`ArraySL`**](#arrayslt) - [**`Cryptography`**](#cryptography) --- ### `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. <details> <summary>Example</summary> ```ts 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")]); ``` </details> --- ### `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): `RetryOptions` - `limit`: `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. <details> <summary>Example</summary> ```ts 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!' } ``` </details> --- ### `ArraySL<T>` An extended `Array` class with convenient getters and powerful utility methods. <details> <summary>Creating an `ArraySL` instance</summary> ```ts import { ArraySL } from "@ugo-code/streamline.js/array"; const numbers = new ArraySL([1, 2, 3, 4, 5]); ``` </details> #### Getters - **`.first`**: Returns the first element. - **`.last`**: Returns the last element. - **`.random`**: Returns a random element. <details> <summary>Example</summary> ```ts const arr = new ArraySL([10, 20, 30]); console.log(arr.first); // 10 console.log(arr.last); // 30 ``` </details> #### Methods - **`.duplicates([options])`**: Returns a new `ArraySL` containing duplicate elements, with configurable modes (`all`, `first`, `subsequent`) and an optional `accessor`. - **`.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 optional `accessor` function. - **`.unique([options])`**: Returns a new `ArraySL` with unique elements based on an optional `accessor` function. <details> <summary>Method Chaining Example</summary> ```ts 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 } ``` </details> --- ### 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: `3600000`ms, 1 hour) - The validity period in milliseconds. Pass `null` to disable expiration. - `pbkdf2Iterations`: `number` (default: `100000`) - The number of iterations for key derivation (PBKDF2). **Warning**: Reducing this value weakens security. - **Throws**: `CryptoError` if the environment is unsupported or encryption fails. <details> <summary>Example</summary> ```ts 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, }); ``` </details> #### `decryptString(encryptedData, secretKey)` Decrypts a string encrypted with `encryptString`, verifying its TTL. - **`encryptedData`**: `string` - The Base64 encoded string from `encryptString`. - **`secretKey`**: `string` - The same secret key used for encryption. - **Throws**: `CryptoError` with specific codes for different failure reasons. <details> <summary>Example</summary> ```ts 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. ``` </details> #### `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`, and `Set` objects (with elements in any order). - Primitives and special values like `Date`, `RegExp`, `BigInt`, `Symbol`, and `undefined`. - 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. <details> <summary>Example</summary> ```ts 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 crashing ``` </details> #### Error 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. <details> <summary>Example of handling a `CryptoError`</summary> ```ts 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); } } ``` </details> ## Contributing Contributions are welcome! Please feel free to submit a pull request or open an issue. 1. Fork the repository. 2. Create your feature branch (`git checkout -b feature/AmazingFeature`). 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`). 4. Push to the branch (`git push origin feature/AmazingFeature`). 5. Open a pull request. ## License This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.