Skip to content

Data Operations

clone()

Deep clone any JavaScript value, including modern types like Maps, Sets, and WeakRefs.

ts
function clone<T>(value: T): T

Example:

ts
import { clone } from '@logosdx/utils'

// Complex state with modern JS types
const appState = {
    users: new Map([
        ['user1', {
            name: 'Alice',
            permissions: new Set(['read', 'write', 'admin']),
            lastLogin: new Date(),
            metadata: new WeakRef(heavyObject)
        }]
    ]),
    config: {
        features: new Map([['darkMode', true]]),
        cache: new WeakMap([[key, value]]),
        history: [
            { timestamp: new Date(), action: 'login' }
        ]
    }
}

// Perfect deep clone that preserves all types
const clonedState = clone(appState)
// Maps are Maps, Sets are Sets, Dates are Dates, WeakRefs are WeakRefs

// Safe state updates
const updateUserPermissions = (state: AppState, userId: string, newPermissions: string[]) => {

    const newState = clone(state)
    const user = newState.users.get(userId)

    if (user) {

        user.permissions = new Set(newPermissions)
        user.lastModified = new Date()
    }

    return newState
}

equals()

Deep equality comparison that handles all JavaScript types correctly.

ts
function equals(a: unknown, b: unknown): boolean

Example:

ts
import { equals } from '@logosdx/utils'

// Compare complex objects
const state1 = {
    users: new Map([['u1', { name: 'Alice', roles: new Set(['admin']) }]]),
    lastSync: new Date('2023-01-01')
}

const state2 = {
    users: new Map([['u1', { name: 'Alice', roles: new Set(['admin']) }]]),
    lastSync: new Date('2023-01-01')
}

console.log(equals(state1, state2)) // true

// Use in React components to prevent unnecessary re-renders
const MyComponent = ({ data }) => {

    const [prevData, setPrevData] = useState(data)

    useEffect(() => {

        if (!equals(prevData, data)) {

            // Data actually changed, update UI
            setPrevData(data)
            updateExpensiveVisualization(data)
        }
    }, [data, prevData])

    // Component only re-renders when data actually changes
}

// Use for offer comparison
const compareOffers = (offer1: Offer, offer2: Offer) => {

    if (equals(offer1.terms, offer2.terms)) {

        return 'These offers have identical terms'
    }

    return 'These offers have different terms'
}

merge()

Deep merge objects while preserving types and handling edge cases.

ts
function merge<T, U>(target: T, source: U, options?: MergeOptions): T & U

interface MergeOptions {
    mergeArrays?: boolean;
    mergeSets?: boolean;
}

Example:

ts
import { merge } from '@logosdx/utils'

// Basic merging
const defaultConfig = {
    api: { timeout: 5000, retries: 3 },
    features: { darkMode: false, beta: false }
}

const userConfig = {
    api: { timeout: 10000 },
    features: { darkMode: true }
}

const finalConfig = merge(defaultConfig, userConfig)
// Result: {
//   api: { timeout: 10000, retries: 3 },
//   features: { darkMode: true, beta: false }
// }

// Advanced merging with modern types
const currentState = {
    users: new Map([['u1', { name: 'Alice' }]]),
    permissions: new Set(['read']),
    history: [{ action: 'login' }]
}

const updates = {
    users: new Map([['u2', { name: 'Bob' }]]),
    permissions: new Set(['write']),
    history: [{ action: 'logout' }]
}

const newState = merge(currentState, updates, {
    mergeArrays: true,
    mergeSets: true
})

// Customer profile merging
const mergeCustomerProfiles = (existing: CustomerProfile, updates: Partial<CustomerProfile>) => {

    return merge(existing, updates, {
        mergeArrays: false,
        mergeSets: false
    })
}

reach()

Type-safe property access with dot notation paths.

ts
function reach<T, P extends PathNames<T>>(obj: T, path: P): PathValue<T, P> | undefined

// Type utilities for path navigation
type PathNames<T> = // All possible dot-notation paths in T
type PathValue<T, P extends string> = // Type at specific path

Example:

ts
import { reach } from '@logosdx/utils'

// Complex nested data
interface CustomerData {
    profile: {
        personal: {
            name: string
            email: string
        }
        preferences: {
            theme: 'light' | 'dark' | 'auto'
            loanTypes: string[]
        }
    }
    loanHistory: {
        totalLoans: number
        averageAmount: number
    }
    metadata: Map<string, any>
}

const customer: CustomerData = {
    profile: {
        personal: { name: 'Alice', email: '[email protected]' },
        preferences: { theme: 'dark', subscriptions: ['personal', 'auto'] }
    },
    loanHistory: { totalLoans: 3, averageAmount: 15000 },
    metadata: new Map([['vip', true]])
}

// Type-safe property access
const name = reach(customer, 'profile.personal.name')           // string | undefined
const email = reach(customer, 'profile.personal.email')        // string | undefined
const theme = reach(customer, 'profile.preferences.theme') // 'light' | 'dark' | 'auto' | undefined
const totalLoans = reach(customer, 'loanHistory.totalLoans')   // number | undefined

// Safe access with fallbacks
const displayName = reach(customer, 'profile.personal.name') ?? 'Anonymous Customer'
const preferredTheme = reach(customer, 'profile.preferences.theme') ?? 'light'
const loanCount = reach(customer, 'loanHistory.totalLoans') ?? 0

// Build type-safe form systems
const createFormField = <T, P extends PathNames<T>>(
    data: T,
    path: P,
    label: string
) => {

    const value = reach(data, path)
    const type = typeof value

    return {
        path,
        label,
        value,
        type,
        required: value === undefined
    }
}

const nameField = createFormField(customer, 'profile.personal.name', 'Full Name')
const emailField = createFormField(customer, 'profile.personal.email', 'Email Address')

setDeep()

Set values deep within nested objects using dot notation paths. Creates intermediate objects automatically.

ts
function setDeep<T, P extends PathNames<T>>(
    obj: T,
    path: P,
    value: PathValue<T, P>
): void

Parameters:

  • obj - Object to modify (mutated in place)
  • path - Dot-separated path to target property
  • value - Value to set at the path

Example:

ts
import { setDeep } from '@logosdx/utils'

// Building configuration incrementally
const config: any = {}

setDeep(config, 'server.port', 3000)
setDeep(config, 'server.host', 'localhost')
setDeep(config, 'database.connection.timeout', 5000)

console.log(config)
// { server: { port: 3000, host: 'localhost' }, database: { connection: { timeout: 5000 } } }

// Setting metrics in monitoring
const metrics: any = { memory: { heap: 100 } }

setDeep(metrics, 'memory.rss', 1024)
setDeep(metrics, 'cpu.user', 50)

console.log(metrics)
// { memory: { heap: 100, rss: 1024 }, cpu: { user: 50 } }

// API response building
function buildSuccessResponse(data: any) {

    const response: any = {}

    setDeep(response, 'status.code', 200)
    setDeep(response, 'status.message', 'OK')
    setDeep(response, 'data.results', data)
    setDeep(response, 'data.timestamp', Date.now())

    return response
}

setDeepMany()

Set multiple values deep within nested objects using dot notation paths. Fails fast on first error with helpful error messages including entry index.

ts
function setDeepMany<T>(
    obj: T,
    entries: Array<[PathNames<T>, any]>
): void

Parameters:

  • obj - Object to modify (mutated in place)
  • entries - Array of [path, value] tuples to set

Example:

ts
import { setDeepMany } from '@logosdx/utils'

// Building complete response objects
const response: any = {}

setDeepMany(response, [
    ['status.code', 200],
    ['status.message', 'OK'],
    ['data.results', [1, 2, 3]],
    ['data.total', 3],
    ['meta.timestamp', Date.now()],
    ['meta.version', '1.0.0']
])

// Complex configuration
const appConfig: any = {}

setDeepMany(appConfig, [
    ['server.port', 3000],
    ['server.host', 'localhost'],
    ['database.url', 'postgres://localhost'],
    ['database.pool.min', 2],
    ['database.pool.max', 10],
    ['features.auth.enabled', true],
    ['features.logging.level', 'info'],
    ['features.monitoring.metrics', true]
])

Error Messages:

Validation errors include entry index for quick debugging:

ts
// Invalid tuple format
setDeepMany(obj, [
    ['valid', 'works'],
    ['invalid']  // entry 1 must be a [path, value] tuple
])

// Empty path
setDeepMany(obj, [
    ['valid', 'works'],
    ['', 'oops']  // entry 1 must have a non-empty string path (received: string)
])

makeNestedConfig()

Transform flat environment variables into nested configuration objects with automatic type coercion. Essential for 12-factor apps and containerized deployments.

ts
function makeNestedConfig<C extends object, F extends Record<string, string>>(
    flatConfig: F,
    opts?: {
        filter?: (key: string, val: string) => boolean
        forceAllCapToLower?: boolean  // Default: true
        separator?: string            // Default: "_"
        stripPrefix?: string | number
        parseUnits?: boolean          // Default: false
        skipConversion?: (key: string, value: unknown) => boolean
    }
): {
    allConfigs: () => C
    getConfig: <P extends PathLeaves<C>>(path: P, defaultValue?: PathValue<C, P>) => PathValue<C, P>
    updateFlatConfig: (override: Partial<F>) => void
    updateParsedConfig: (override: DeepOptional<C>) => void
    setDeepInParsedConfig: (entries: Array<[PathNames<DeepOptional<C>>, unknown]>) => void
}

Example:

ts
import { makeNestedConfig } from '@logosdx/utils'

// Define expected config shape for type safety
type AppConfig = {
    db: { host: string; port: number };
    debug: boolean;
    feature: { x: { enabled: boolean } };
    worker: { emails: { maxRunsPerMin: number } };
}

const config = makeNestedConfig<AppConfig>(process.env, {
    filter: (key) => key.startsWith('APP_'),
    stripPrefix: 'APP_',
    forceAllCapToLower: true
})

console.log(config.allConfigs())
// {
//   db: { host: 'localhost', port: 5432 },
//   debug: true,
//   feature: { x: { enabled: false } },
//   worker: { emails: { maxRunsPerMin: 100 } }
// }

// Reach into config with a type-safe path parameter
const dbHost = config.getConfig('db.host')                       // 'localhost'
const dbPort = config.getConfig('db.port')                       // 5432
const isDebug = config.getConfig('debug')                        // true
const maxRuns = config.getConfig('worker.emails.maxRunsPerMin')  // 100

// Use default values for missing configuration
const apiTimeout = config.getConfig('api.timeout', 5000)   // 5000 (default)
const maxRetries = config.getConfig('api.retries', 3)      // 3 (default)
const logLevel = config.getConfig('logging.level', 'info') // 'info' (default)

// Parse unit values (time durations and byte sizes)
// Given: APP_TIMEOUT='5m', APP_MAX_SIZE='10mb'
const configWithUnits = makeNestedConfig(process.env, {
    filter: (key) => key.startsWith('APP_'),
    stripPrefix: 'APP_',
    parseUnits: true
})
// configWithUnits.allConfigs() -> { timeout: 300000, max: { size: 10485760 } }

Caching and invalidation:

The first call to allConfigs() (or getConfig(), which calls it internally) parses the flatmap and caches the result. Subsequent calls skip the parse and coercion pass, but every call returns a detached copy — mutating a returned object never affects the cache or later reads.

ts
const a = config.allConfigs()
const b = config.allConfigs()  // no re-parse, but a !== b — each read is a detached copy

a.db.host = 'mutated'          // safe: does not corrupt the cached config
config.getConfig('db.host')    // still the parsed value

updateFlatConfig(), updateParsedConfig(), and setDeepInParsedConfig() (when given a non-empty entries array) invalidate the cache, so the next allConfigs()/getConfig() call re-parses.

Updating configuration at runtime:

Three functions layer overrides on top of the flatmap-parsed config — useful for tests, feature flags, or reacting to a config reload:

ts
// Deep-merge a partial override into the flat source, then invalidate
config.updateFlatConfig({ APP_DB_HOST: 'db.internal' })

// Deep-merge a partial override into the parsed shape (nested objects allowed)
config.updateParsedConfig({ db: { host: 'db.internal' } })

// Write specific paths into the parsed override without touching siblings
config.setDeepInParsedConfig([
    ['db.host', 'db.internal'],
    ['debug', false]
])

updateParsedConfig() and setDeepInParsedConfig() write into the same accumulated override object: each call merges on top of what came before, and the override survives a later updateFlatConfig() + re-parse — it's re-applied after every parse. updateFlatConfig() and updateParsedConfig() both throw if override is not a non-null object.

Layering order: every re-parse rebuilds the config from the flat source first, then merges the accumulated updateParsedConfig()/setDeepInParsedConfig() override on top — parsed-level overrides always win over flat values, including flat values changed afterward by updateFlatConfig().


castValuesToTypes()

Intelligently coerce string values to their appropriate types. Recursively processes nested objects.

ts
function castValuesToTypes(
    obj: object,
    opts?: {
        parseUnits?: boolean  // Default: false
        skipConversion?: (key: string, value: unknown) => boolean
    }
): void  // Mutates in place

Example:

ts
import { castValuesToTypes } from '@logosdx/utils'

const config = {
    debug: 'true',        // -> true
    verbose: 'yes',       // -> true
    silent: 'false',      // -> false
    disabled: 'no',       // -> false
    port: '3000',         // -> 3000
    timeout: '5000',      // -> 5000
    name: 'myapp',        // -> 'myapp' (unchanged)
    nested: {
        enabled: 'true',  // -> true (recursive)
        retries: '5'      // -> 5 (recursive)
    }
}

castValuesToTypes(config)  // Mutates in place

console.log(config.debug)  // true (boolean)
console.log(config.port)   // 3000 (number)

// Parse unit values
const configWithUnits = {
    timeout: '5m',
    maxSize: '10mb',
    debug: 'true'
}

castValuesToTypes(configWithUnits, { parseUnits: true })
console.log(configWithUnits.timeout)  // 300000 (5 minutes in ms)
console.log(configWithUnits.maxSize)  // 10485760 (10 MB in bytes)

// Skip conversion for specific keys
const configWithSkip = {
    apiKey: '12345',
    port: '3000'
}

castValuesToTypes(configWithSkip, {
    skipConversion: (key) => key.toLowerCase().includes('key')
})
console.log(configWithSkip.apiKey)  // '12345' (kept as string)
console.log(configWithSkip.port)    // 3000 (converted to number)

Recognized values:

  • Enabled: "true", "yes", true
  • Disabled: "false", "no", false
  • Numbers: Strings containing only digits (/^\d+$/)

isEnabledValue() / isDisabledValue()

Check if a value represents an enabled or disabled state.

ts
function isEnabledValue(val: unknown): boolean
function isDisabledValue(val: unknown): boolean
function hasEnabledOrDisabledValue(val: unknown): boolean

Example:

ts
import { isEnabledValue, isDisabledValue } from '@logosdx/utils'

// Environment variable checking
if (isEnabledValue(process.env.DEBUG)) {
    enableDebugMode()
}

if (isDisabledValue(process.env.FEATURE_FLAG)) {
    skipFeature()
}

// Configuration validation
const config = {
    featureA: 'true',
    featureB: 'no',
    featureC: 'maybe'
}

Object.entries(config).forEach(([key, value]) => {
    if (isEnabledValue(value)) {
        console.log(`${key}: enabled`)
    } else if (isDisabledValue(value)) {
        console.log(`${key}: disabled`)
    } else {
        console.warn(`${key}: invalid value`)
    }
})

Unit Conversion & Formatting

Human-readable time and byte size utilities for configuration, logging, and display.

Time Units

ts
// Constants
const timeUnits: {
    sec: number, min: number, hour: number, day: number, week: number
    secs(n: number): number, mins(n: number): number, hours(n: number): number
    days(n: number): number, weeks(n: number): number
}

// Convenience functions
const seconds: (n: number) => number
const minutes: (n: number) => number
const hours: (n: number) => number
const days: (n: number) => number
const weeks: (n: number) => number
const months: (n: number) => number
const years: (n: number) => number

// Parse human-readable strings to milliseconds
const parseTimeDuration: (str: string) => number

// Format milliseconds to human-readable strings
const formatTimeDuration: (ms: number, opts?: {
    decimals?: number
    unit?: 'sec' | 'min' | 'hour' | 'day' | 'week' | 'month' | 'year'
}) => string

Example:

ts
import {
    seconds, minutes, hours,
    parseTimeDuration,
    formatTimeDuration
} from '@logosdx/utils'

// Programmatic duration calculation
setTimeout(cleanup, minutes(5))     // 5 minutes = 300000ms
setInterval(poll, seconds(30))      // 30 seconds = 30000ms
cache.set(key, value, { ttl: hours(1) })  // 1 hour

// Parse configuration from environment variables
const config = {
    sessionTimeout: parseTimeDuration(process.env.SESSION_TIMEOUT || '1hour'),
    cacheExpiry: parseTimeDuration(process.env.CACHE_TTL || '15min'),
    heartbeat: parseTimeDuration(process.env.HEARTBEAT || '30sec')
}

// Supports multiple formats
parseTimeDuration('30sec')       // 30000
parseTimeDuration('30 secs')     // 30000
parseTimeDuration('30 seconds')  // 30000
parseTimeDuration('5m')          // 300000
parseTimeDuration('5min')        // 300000
parseTimeDuration('2.5 hours')   // 9000000 (decimals supported)

// Format for display (auto-selects unit)
formatTimeDuration(1000)          // "1sec"
formatTimeDuration(90000)         // "1.5min" (smart decimals)
formatTimeDuration(3600000)       // "1hour"

// Control formatting
formatTimeDuration(90000, { unit: 'sec' })      // "90sec"
formatTimeDuration(90000, { decimals: 0 })      // "2min"

// Logging with readable durations
logger.info(`Cache expires in: ${formatTimeDuration(cache.ttl)}`)
logger.debug(`Request took: ${formatTimeDuration(elapsed)}`)

Byte Sizes

ts
// Constants
const byteUnits: {
    kb: number, mb: number, gb: number, tb: number
    kbs(n: number): number, mbs(n: number): number
    gbs(n: number): number, tbs(n: number): number
}

// Convenience functions
const kilobytes: (n: number) => number
const megabytes: (n: number) => number
const gigabytes: (n: number) => number
const terabytes: (n: number) => number

// Parse human-readable strings to bytes
const parseByteSize: (str: string) => number

// Format bytes to human-readable strings
const formatByteSize: (bytes: number, opts?: {
    decimals?: number  // Default: 2
    unit?: 'kb' | 'mb' | 'gb' | 'tb'
}) => string

Example:

ts
import {
    megabytes, kilobytes,
    parseByteSize,
    formatByteSize
} from '@logosdx/utils'

// Programmatic size calculation
const maxFileSize = megabytes(10)  // 10485760 bytes
const bufferSize = kilobytes(64)   // 65536 bytes

// Parse configuration
const config = {
    uploadLimit: parseByteSize(process.env.MAX_UPLOAD || '10mb'),
    diskQuota: parseByteSize(process.env.DISK_QUOTA || '100gb'),
    thumbnailMax: parseByteSize(process.env.THUMB_SIZE || '500kb')
}

// Supports multiple formats
parseByteSize('10kb')           // 10240
parseByteSize('10 kbs')         // 10240
parseByteSize('10 kilobytes')   // 10240
parseByteSize('2.5gb')          // 2684354560 (decimals supported)

// Format for display (auto-selects unit)
formatByteSize(1024)            // "1kb"
formatByteSize(1536)            // "1.5kb"
formatByteSize(10485760)        // "10mb"

// Control formatting
formatByteSize(1024, { unit: 'mb' })        // "0mb"
formatByteSize(1536, { decimals: 0 })       // "2kb"

// Display file sizes
files.forEach(file => {
    console.log(`${file.name}: ${formatByteSize(file.size)}`)
})

Array Utilities

chunk()

Split an array into smaller arrays of the specified size.

ts
function chunk<T>(array: T[], size: number): T[][]

Example:

ts
import { chunk } from '@logosdx/utils'

chunk([1, 2, 3, 4, 5, 6, 7], 3) // [[1, 2, 3], [4, 5, 6], [7]]
chunk(['a', 'b', 'c', 'd'], 2)   // [['a', 'b'], ['c', 'd']]

// Process large datasets in batches
async function processBatches(items: any[], batchSize = 10) {

    const batches = chunk(items, batchSize)

    for (const batch of batches) {

        await Promise.all(batch.map(processItem))
        console.log(`Processed batch of ${batch.length} items`)
    }
}

// Paginate results
function paginateResults<T>(results: T[], pageSize = 20) {

    const pages = chunk(results, pageSize)

    return pages.map((page, index) => ({
        page: index + 1,
        data: page,
        hasNext: index < pages.length - 1
    }))
}

itemsToArray()

Normalize input to always return an array. Wraps single items, passes arrays through.

ts
function itemsToArray<T>(items: T | T[]): T[]

Example:

ts
import { itemsToArray } from '@logosdx/utils'

itemsToArray('single')             // ['single']
itemsToArray(['already', 'array']) // ['already', 'array']
itemsToArray(42)                   // [42]

// Accept flexible input in your APIs
function processFiles(files: string | string[]) {

    const fileArray = itemsToArray(files)

    for (const file of fileArray) {
        console.log(`Processing: ${file}`)
    }
}

processFiles('single.txt')              // Works with single file
processFiles(['file1.txt', 'file2.txt']) // Works with multiple files

oneOrMany()

Unwrap single-item arrays to the item itself. Returns the array unchanged if it has more than one element.

ts
function oneOrMany<T>(items: T[]): T | T[]

Example:

ts
import { oneOrMany } from '@logosdx/utils'

oneOrMany(['single'])           // 'single'
oneOrMany(['multiple', 'items']) // ['multiple', 'items']
oneOrMany([])                   // []

function findUsers(query: string): User | User[] {

    const results = database.search(query)
    return oneOrMany(results)
}

Object Property Utilities

definePublicProps()

Define visible, non-writable properties on an object. Properties show up in Object.keys() and for...in loops but cannot be modified.

ts
function definePublicProps<T, U extends Record<string, unknown>>(
    target: T,
    props: U,
    configurable?: boolean  // default: false
): void

Example:

ts
import { definePublicProps } from '@logosdx/utils'

const api = {}

definePublicProps(api, {
    version: '1.0.0',
    name: 'MyAPI'
})

console.log(api.version)       // '1.0.0'
console.log(Object.keys(api))  // ['version', 'name']
api.version = '2.0.0'          // Fails silently or throws in strict mode

// Immutable instance properties
class DataProcessor {

    constructor(config) {

        definePublicProps(this, {
            id: crypto.randomUUID(),
            createdAt: new Date()
        })
    }
}

definePrivateProps()

Define hidden, non-writable properties on an object. Properties are not enumerable (hidden from Object.keys() and for...in) but still accessible by name.

ts
function definePrivateProps<T, U extends Record<string, unknown>>(
    target: T,
    props: U,
    configurable?: boolean  // default: false
): void

Example:

ts
import { definePrivateProps } from '@logosdx/utils'

const api = {}

definePrivateProps(api, {
    _cache: new Map(),
    _getId: () => crypto.randomUUID()
})

console.log(Object.keys(api)) // [] (hidden properties)
console.log(api._cache)       // Map instance (accessible but hidden)

class EventEmitter {

    constructor() {

        definePrivateProps(this, {
            _listeners: new Map(),
            _emit: this.emit.bind(this)
        })
    }
}

definePrivateGetters()

Define hidden getter properties on an object. Useful for computed properties and lazy-loaded values.

ts
function definePrivateGetters<T, U extends Record<string, Func>>(
    target: T,
    props: U,
    configurable?: boolean  // default: false
): void

Example:

ts
import { definePrivateGetters } from '@logosdx/utils'

const user = { firstName: 'John', lastName: 'Doe' }

definePrivateGetters(user, {
    _fullName: () => `${user.firstName} ${user.lastName}`,
    _initials: () => `${user.firstName[0]}${user.lastName[0]}`
})

console.log(user._fullName)    // 'John Doe'
console.log(Object.keys(user)) // ['firstName', 'lastName'] (getters hidden)

class DataProcessor {

    constructor(data) {

        this.data = data

        definePrivateGetters(this, {
            _size: () => this.data.length,
            _isEmpty: () => this.data.length === 0
        })
    }
}

PriorityQueue

A min-heap priority queue with O(log n) push/pop operations. Lower priority numbers dequeue first. Ties are broken by insertion order (FIFO by default).

ts
class PriorityQueue<T> {
    constructor(options?: PriorityQueueOptions<T>)

    push(value: T, priority?: number): void    // Add item (default priority: 0)
    pop(): T | null                            // Remove highest-priority item
    popMany(count?: number): T[]               // Remove multiple items
    peek(): T | null                           // View next item without removing
    peekMany(count?: number): T[]              // View multiple items
    find(predicate: (value: T) => boolean): T | null

    heapify(items: Node<T>[]): void            // Build heap from array
    clone(): PriorityQueue<T>                  // Independent copy
    toSortedArray(): T[]                       // Extract sorted array

    size(): number
    isEmpty(): boolean
    clear(): void

    [Symbol.iterator](): IterableIterator<T>   // Iterate in priority order
}

interface PriorityQueueOptions<T> {
    lifo?: boolean      // LIFO for equal priorities (default: false/FIFO)
    compare?: (a: Node<T>, b: Node<T>) => number  // Custom comparator
    maxHeap?: boolean   // Invert priority order (default: false)
}

Example:

ts
import { PriorityQueue } from '@logosdx/utils'

// Task scheduling
const taskQueue = new PriorityQueue<{ name: string; deadline: Date }>()

taskQueue.push({ name: 'Critical fix', deadline: new Date() }, 1)
taskQueue.push({ name: 'Nice to have', deadline: new Date() }, 10)
taskQueue.push({ name: 'Urgent review', deadline: new Date() }, 2)

taskQueue.peek()  // { name: 'Critical fix', ... } (priority 1)
taskQueue.pop()   // { name: 'Critical fix', ... }
taskQueue.pop()   // { name: 'Urgent review', ... } (priority 2)
taskQueue.pop()   // { name: 'Nice to have', ... } (priority 10)

// Max-heap (highest priority first)
const leaderboard = new PriorityQueue<string>({ maxHeap: true })

leaderboard.push('Alice', 95)
leaderboard.push('Bob', 87)
leaderboard.push('Charlie', 92)

leaderboard.pop() // 'Alice' (score 95)
leaderboard.pop() // 'Charlie' (score 92)

// Iterate in priority order
const queue = new PriorityQueue<string>()
queue.push('low', 3)
queue.push('high', 1)
queue.push('medium', 2)

for (const item of queue) {
    console.log(item) // 'high', 'medium', 'low'
}

// LIFO tie-breaking
const lifoQueue = new PriorityQueue<string>({ lifo: true })
lifoQueue.push('first', 1)
lifoQueue.push('second', 1)
lifoQueue.pop() // 'second' (newest with same priority)

Miscellaneous Utilities

noop

A no-operation function that accepts any arguments and returns nothing. Useful as a default callback.

ts
const noop: (...args: any[]) => any

Example:

ts
import { noop } from '@logosdx/utils'

// Default callback
function createHandler(onSuccess = noop, onError = noop) {
    // ...
}

// Disable logging in production
const logger = isProduction ? { log: noop, warn: noop, error: noop } : console

generateId()

Generates a random ID string. Simple and fast, suitable for non-cryptographic use.

ts
function generateId(): string  // Returns '_' + random alphanumeric (e.g., '_a3f7k2m')

Example:

ts
import { generateId } from '@logosdx/utils'

const id = generateId() // '_a3f7k2m'

// Use for temporary identifiers
const tempElements = items.map(item => ({
    key: generateId(),
    ...item
}))

nTimes()

Execute a function N times and return the results as an array. The function receives the current iteration index.

ts
function nTimes<T>(fn: (iteration: number) => T, n: number): T[]

Example:

ts
import { nTimes } from '@logosdx/utils'

nTimes(() => createEl('span'), 3)    // [span, span, span]
nTimes(() => Math.random(), 5)       // [0.12, 0.45, 0.78, 0.23, 0.91]
nTimes((i) => (i + 1) * 2, 3)       // [2, 4, 6]

// Generate test data
const mockUsers = nTimes((i) => ({
    id: `user-${i}`,
    name: `User ${i + 1}`,
    email: `user${i + 1}@example.com`
}), 10)