@hyperfrontend/function-utils§

Four vertical lanes, each with a send box in the middle, and the same three numbered tokens dropped into all four at once while an online switch in the margin goes off for the second call: the bare lane throws and dies, the run-once lane sends its first token through and bypasses the rest with the cached 1, the conditional lane stops the second token at a shut gate and passes the third, and the error-ignoring lane absorbs the throw behind a shield and carries onFour vertical lanes, each with a send box in the middle, and the same three numbered tokens dropped into all four at once while an online switch in the margin goes off for the second call: the bare lane throws and dies, the run-once lane sends its first token through and bypasses the rest with the cached 1, the conditional lane stops the second token at a shut gate and passes the third, and the error-ignoring lane absorbs the throw behind a shield and carries on

Higher-order function utilities for behavioral modification and composition.

What is @hyperfrontend/function-utils?

@hyperfrontend/function-utils provides lightweight functional wrappers that modify the execution behavior of functions without changing their signatures. The library focuses on common patterns like single-execution guarantees, conditional execution, error suppression, and no-op placeholders, all implemented as composable higher-order functions.

Each utility maintains the original function's type signature through generic constraints, ensuring type safety while adding behavioral modifications. The implementations use closure-based state management to track execution context (like memoized results or call counts) without external dependencies or complex class hierarchies.

Key Features

  • Run-once enforcement

    Memoize first result and prevent subsequent executions (lazy initialization, singleton setup)

  • Conditional execution

    Guard function calls behind runtime predicates without inline conditionals

  • Error suppression

    Silent exception handling for void functions where failures are acceptable

  • No-op placeholder

    Type-safe no-operation function for default parameters and optional callbacks

  • Full type preservation

    Generic constraints maintain original function signatures through transformations

  • Zero dependencies

    Self-contained higher-order functions with no external dependencies

  • Minimal overhead

    Simple closure-based implementations with negligible performance impact

  • TypeScript native

    Complete type inference with comprehensive JSDoc documentation

Why Use @hyperfrontend/function-utils?

1. Declarative Run-Once Logic Without Manual Guards

Initialization and setup functions often need single-execution guarantees (database connections, singleton initialization, one-time DOM setup). Manual implementation requires scattered boolean flags and conditional checks throughout the codebase. createRunOnceFunction() encapsulates this pattern in a reusable wrapper that memoizes the first result and prevents re-execution.

Example pain point solved: Instead of writing if (initialized) return cachedValue; initialized = true; cachedValue = expensiveSetup(); return cachedValue repeatedly, wrap the function once and call it freely.

2. Separation of Business Logic from Execution Conditions

Mixing business logic with execution conditions (feature flags, permission checks, environment guards) creates coupling and reduces testability. createConditionalExecutionFunction() separates the condition from the action, enabling independent testing of the function logic and the condition predicate.

Example: Logging functions that should only execute in development environments can be wrapped once with an isDevelopment() condition rather than checking the environment at every call site.

3. Intentional Error Suppression for Non-Critical Operations

Some operations (analytics tracking, debug logging, experimental features) should never crash the application if they fail. createErrorIgnoringFunction() explicitly documents error suppression intent through the wrapper, making it clear that failures are expected and acceptable, unlike bare try-catch blocks scattered through code.

Example: Analytics tracking that fails due to network issues shouldn't crash the app. Wrapping the tracking function makes the error suppression explicit and centralized.

4. Foundation for Logger Error Handling

The @hyperfrontend/logging library uses createErrorIgnoringFunction() and createConditionalExecutionFunction() extensively to prevent logging failures from crashing applications and to implement log level filtering. These utilities enable the logger to be resilient and configurable without defensive try-catch blocks throughout the logging implementation.

5. Type-Safe No-Op for Optional Callbacks

Default parameters and optional callback patterns often require placeholder no-op functions. Using () => {} loses type information and creates subtle bugs when functions expect specific signatures. The noop utility provides a type-safe placeholder that accepts any arguments and returns void, working correctly as a default for any callback pattern.

Installation

npm install @hyperfrontend/function-utils

Quick Start

Run-once initialization:

import { createRunOnceFunction } from '@hyperfrontend/function-utils'

// Expensive setup function
function initializeDatabase() {
  console.log('Connecting to database...')
  return { connection: 'db-handle' }
}

// Wrap to ensure single execution
const getDatabase = createRunOnceFunction(initializeDatabase)

// First call executes and caches result
const db1 = getDatabase() // Logs "Connecting..."
// Subsequent calls return cached result
const db2 = getDatabase() // No log, returns cached connection
console.log(db1 === db2) // true

Conditional execution:

import { createConditionalExecutionFunction } from '@hyperfrontend/function-utils'

// Business logic function
function trackAnalytics(event: string, data: object) {
  console.log('Tracking:', event, data)
  // ... send to analytics service
}

// Wrap with feature flag condition
const isDevelopment = () => process.env.NODE_ENV === 'development'
const trackInDev = createConditionalExecutionFunction(trackAnalytics, isDevelopment)

// Only executes in development
trackInDev('user_click', { button: 'submit' }) // Executes in dev, skipped in prod

Error suppression:

import { createErrorIgnoringFunction } from '@hyperfrontend/function-utils'

// Non-critical operation that might fail
function updateLocalCache(key: string, value: any): void {
  localStorage.setItem(key, JSON.stringify(value)) // May throw in incognito
}

// Wrap to prevent crashes
const safeUpdateCache = createErrorIgnoringFunction(updateLocalCache)

// App continues even if localStorage fails
safeUpdateCache('user-prefs', { theme: 'dark' }) // Never throws

No-op placeholder:

import { noop } from '@hyperfrontend/function-utils'

// Function with optional callback
function fetchData(url: string, onSuccess = noop, onError = noop) {
  fetch(url)
    .then((response) => response.json())
    .then(onSuccess)
    .catch(onError)
}

// Caller can omit callbacks safely
fetchData('/api/data') // Uses noop for both callbacks

Composing multiple wrappers:

import { createRunOnceFunction, createConditionalExecutionFunction } from '@hyperfrontend/function-utils'

// Only execute expensive setup once, and only in production
const isProduction = () => process.env.NODE_ENV === 'production'

const setupAnalytics = createRunOnceFunction(
  createConditionalExecutionFunction(() => {
    console.log('Initializing analytics...')
    return { initialized: true }
  }, isProduction)
)

// First call in production initializes, subsequent calls return cached result
// In development, all calls skip execution

API Overview

The surface is three higher-order wrappers and one placeholder, and nothing else: no configuration objects, no registry, no state shared between two wrappers. Each wrapper takes a function and gives back a function you call exactly the way you called the original, because its parameters are typed Parameters<T> and its result is derived from ReturnType<T>. What a wrapper changes is when, or whether, the underlying call happens.

Start with createRunOnceFunction: it invokes the wrapped function on the first call, stores that result and returns the stored one for every call after, ignoring their arguments entirely. createConditionalExecutionFunction takes a second argument, a no-argument predicate that is re-read on every call, and calls through only while it returns true; it is the piece behind log-level filtering and environment guards. createErrorIgnoringFunction puts the call inside a try with an empty catch, for work you would rather lose than crash on: analytics beacons, cache writes, debug output.

One typing detail decides between them. Run-once returns ReturnType<T>, and error-ignoring is constrained to functions that already return void, so it returns void too and quietly discards anything a wrapped function hands back. The conditional wrapper is the only one that widens the return type, to ReturnType<T> | void: when its predicate is false it never calls through, and the caller receives undefined, so a skipped call is a value you can branch on rather than something to infer.

noop wraps nothing. It takes any arguments and returns nothing, as a typed default for an optional callback parameter.

Full signatures, generic constraints and per-function examples are in the API reference.

Type Safety

All utilities preserve type information through generic constraints:

// Original function type is preserved
function add(a: number, b: number): number {
  return a + b
}

const addOnce = createRunOnceFunction(add)
// Type: (a: number, b: number) => number ✓

const result: number = addOnce(2, 3) // Type safe ✓

Compatibility

Runs on

  • Node.js>=18.0.0supported
  • Browserssupported
  • Web Workerssupported

Ships as

  • ESMTree-shakeable
  • CJSNode and older bundlers
  • IIFEHyperfrontendFunctionUtils
  • UMDHyperfrontendFunctionUtils

CDN Usage

<!-- unpkg -->
<script src="https://unpkg.com/@hyperfrontend/function-utils"></script>

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@hyperfrontend/function-utils"></script>

<script>
  const { createRunOnceFunction, noop } = HyperfrontendFunctionUtils
  const { createErrorIgnoringFunction } = HyperfrontendFunctionUtils
</script>

Global variable: HyperfrontendFunctionUtils

Dependencies

None: zero external dependencies.

API Reference§

Filter:

ƒFunctions

§function

createConditionalExecutionFunction<T>(func: T, conditionFunc: () => boolean): (args: Parameters<T>) => void | ReturnType<T>

Creates a wrapper function that only executes the wrapped function if the condition function returns true. The wrapped function is called with the receiver the wrapper was invoked on, so a method wrapped in place keeps its this.
Parameters
NameTypeDescription
§func
TThe function to be conditionally executed.
§conditionFunc
() => booleanA function that returns a boolean, determining if func should be executed.
Returns
(args: Parameters<T>) => void | ReturnType<T>
A wrapped version of func that executes conditionally.
Examples

Conditional logging based on flag

let enabled = false
const conditionalLog = createConditionalExecutionFunction(console.log, () => enabled)
conditionalLog('test') // does nothing
enabled = true
conditionalLog('test') // logs 'test'

Gating a method you do not own

// an analytics client from a vendor SDK; track() keeps its state on the client itself
const analytics = { queue: [] as string[], track(event: string) { this.queue.push(event) } }
const consent = { granted: false }
analytics.track = createConditionalExecutionFunction(analytics.track, () => consent.granted)
analytics.track('page-view') // dropped while consent is withheld
consent.granted = true
analytics.track('click') // reaches the SDK method, which still finds its queue through `this`
§function

createErrorIgnoringFunction<T>(func: T): (args: Parameters<T>) => void

Creates a wrapper function that silently ignores any errors thrown by the wrapped void function. This function is specifically for wrapping functions that do not return a value (void functions). Exceptions are swallowed without any logging or handling, as is the rejection of a promise the function returns. The wrapped function is called with the receiver the wrapper was invoked on, so a method wrapped in place keeps its this.
Parameters
NameTypeDescription
§func
TThe void function to be wrapped.
Returns
(args: Parameters<T>) => void
A wrapped version of the input function that ignores errors.
Examples

Safely parsing invalid JSON

const safeParse = createErrorIgnoringFunction(() => JSON.parse('invalid'))
safeParse() // silently fails without throwing

Sending a beacon that may fail

const reportMetric = createErrorIgnoringFunction(async (name: string, value: number) => {
  await fetch('/metrics', { method: 'POST', body: JSON.stringify({ name, value }) })
})
reportMetric('render-ms', 12) // a failed request rejects the promise, and that rejection is ignored too
§function

createRunOnceFunction<T>(func: T): (args: Parameters<T>) => ReturnType<T>

Creates a wrapper function around a given function to ensure that it can be executed only once. The result of the first execution is stored and returned for all subsequent calls. This makes the function ideal for use cases where a particular operation, such as an initialization or setup, must be performed only once, and its result reused thereafter. This is useful in scenarios like setting up configurations, initializing singletons, or similar one-time operations. The wrapped function is called with the receiver the wrapper was invoked on, so a method wrapped in place keeps its this.
Parameters
NameTypeDescription
§func
TThe function to be wrapped for single execution.
Returns
(args: Parameters<T>) => ReturnType<T>
A wrapped version of the input function that executes once and returns the same result for all subsequent calls.
Examples

Single execution initialization

const initialize = createRunOnceFunction(() => {
  console.log('Initializing...')
  return { ready: true }
})
initialize() // logs 'Initializing...', returns { ready: true }
initialize() // returns { ready: true } (no re-initialization)

Memoizing a method you do not own

// a client from a vendor SDK; loadConfig() reads the endpoint it was constructed with
const client = { endpoint: 'https://api.example.com', loadConfig() { return fetch(`${this.endpoint}/config`) } }
client.loadConfig = createRunOnceFunction(client.loadConfig)
const first = client.loadConfig() // one request, made against the client's own endpoint
const again = client.loadConfig() // the same promise, no second request
first === again // => true
§function

noop(...args: unknown[]): void

A no-operation function (noop) that does nothing regardless of the arguments passed. It is designed to be as permissive as possible in its typing without using the Function keyword.
Parameters
NameTypeDescription
§...args
unknown[]Any arguments passed to the function (ignored)
Example

Using noop as fallback callback

const callback = condition ? handleEvent : noop
callback() // safely does nothing if condition is false

No guides cover @hyperfrontend/function-utils yet.Request one