MDK Logo

Utilities

Helper functions exported by @tetherto/mdk-react-devkit/core (formatting, validation, conversions), @tetherto/mdk-react-devkit/foundation (settings persistence), and @tetherto/mdk-ui-core (alert query builders and time-range chunking)

This page documents helper functions exported by the MDK packages.

  • Core utilities ships 15 utility modules with functions for formatting, dates, validation, conversions, class-name merging, and more
  • Foundation utilities currently ships a single public utility module (settings-utils) for parsing, validating, and exporting settings JSON
  • UI core alert utilities ships query-parameter builders and time-range chunking helpers for the Alerts feature

Core utilities

Helper functions exported by @tetherto/mdk-react-devkit/core for formatting, dates, validation, conversions, and class-name merging.

Prerequisites

Import

@tetherto/mdk-react-devkit/core
import {
  formatNumber,
  formatHashrate,
  formatDate,
  formatRelativeTime,
  cn,
  isEmpty,
  isValidEmail,
} from '@tetherto/mdk-react-devkit/core'

Formatting utilities

formatNumber

@tetherto/mdk-react-devkit/core

Format numbers with locale formatting and configurable options.

import { formatNumber, FALLBACK } from '@tetherto/mdk-react-devkit/core'

formatNumber(1234.567)                          // "1,234.57"
formatNumber(null)                              // "-"
formatNumber(1234, { minimumFractionDigits: 2 }) // "1,234.00"
formatNumber(undefined, {}, 'N/A')              // "N/A"

formatHashrate

@tetherto/mdk-react-devkit/core

Format hashrate values with rounding.

import { formatHashrate } from '@tetherto/mdk-react-devkit/core'

formatHashrate(150.456)  // "150.46"
formatHashrate(null)     // "-"

formatCurrency

@tetherto/mdk-react-devkit/core

Format currency values.

import { formatCurrency } from '@tetherto/mdk-react-devkit/core'

formatCurrency(1234.56, 'USD')      // "$1,234.56"
formatCurrency(0.00012345, 'BTC')   // "₿0.00012345"

getPercentFormattedNumber

@tetherto/mdk-react-devkit/core

Format numbers as percentages.

import { getPercentFormattedNumber } from '@tetherto/mdk-react-devkit/core'

getPercentFormattedNumber(0.75)      // "75%"
getPercentFormattedNumber(0.1234, 1) // "12.3%"

formatValueUnit

@tetherto/mdk-react-devkit/core

Format value-unit objects.

import { formatValueUnit } from '@tetherto/mdk-react-devkit/core'

formatValueUnit(150, 'TH/s')  // "150 TH/s"

Date utilities

formatDate

@tetherto/mdk-react-devkit/core

Format dates with customizable patterns.

import { formatDate } from '@tetherto/mdk-react-devkit/core'

formatDate(new Date())                              // "Jan 15, 2025"
formatDate(1705334400000, { format: 'yyyy-MM-dd' }) // "2025-01-15"

formatRelativeTime

@tetherto/mdk-react-devkit/core

Format dates as relative time strings.

import { formatRelativeTime } from '@tetherto/mdk-react-devkit/core'

formatRelativeTime(new Date(Date.now() - 3600000))  // "1h ago"
formatRelativeTime(new Date(Date.now() - 86400000)) // "1d ago"

formatChartDate

@tetherto/mdk-react-devkit/core

Format timestamps for chart display.

import { formatChartDate } from '@tetherto/mdk-react-devkit/core'

formatChartDate(1705334400)        // "Jan 15"
formatChartDate(1705334400, true)  // "Jan 15, 2025"

isValidTimestamp

@tetherto/mdk-react-devkit/core

Check if a timestamp is valid.

import { isValidTimestamp } from '@tetherto/mdk-react-devkit/core'

isValidTimestamp(1705334400000)  // true
isValidTimestamp('invalid')      // false

parseMonthLabelToDate

@tetherto/mdk-react-devkit/core

Parse month labels to Date objects.

import { parseMonthLabelToDate } from '@tetherto/mdk-react-devkit/core'

parseMonthLabelToDate('01-26')    // Date(2026, 0, 1)
parseMonthLabelToDate('03-2025')  // Date(2025, 2, 1)

getPastDateFromDate

@tetherto/mdk-react-devkit/core

Get a date in the past.

import { getPastDateFromDate } from '@tetherto/mdk-react-devkit/core'

getPastDateFromDate({ dateTs: Date.now(), days: 7 })  // 7 days ago

Validation utilities

isEmpty

@tetherto/mdk-react-devkit/core

Check if a value is empty.

import { isEmpty } from '@tetherto/mdk-react-devkit/core'

isEmpty(null)       // true
isEmpty('')         // true
isEmpty([])         // true
isEmpty({})         // true
isEmpty('hello')    // false
isEmpty([1, 2, 3])  // false

isValidEmail

@tetherto/mdk-react-devkit/core

Validate email addresses.

import { isValidEmail } from '@tetherto/mdk-react-devkit/core'

isValidEmail('user@example.com')  // true
isValidEmail('invalid')           // false

isValidUrl

@tetherto/mdk-react-devkit/core

Validate URLs.

import { isValidUrl } from '@tetherto/mdk-react-devkit/core'

isValidUrl('https://example.com')  // true
isValidUrl('not-a-url')            // false

isNil

@tetherto/mdk-react-devkit/core

Check if value is null or undefined.

import { isNil } from '@tetherto/mdk-react-devkit/core'

isNil(null)       // true
isNil(undefined)  // true
isNil(0)          // false
isNil('')         // false

isPlainObject

@tetherto/mdk-react-devkit/core

Check if value is a plain object.

import { isPlainObject } from '@tetherto/mdk-react-devkit/core'

isPlainObject({})           // true
isPlainObject({ a: 1 })     // true
isPlainObject([])           // false
isPlainObject(new Date())   // false

Class name utilities

cn

@tetherto/mdk-react-devkit/core

Merge class names using clsx and tailwind-merge.

import { cn } from '@tetherto/mdk-react-devkit/core'

cn('px-4', 'py-2')                    // "px-4 py-2"
cn('text-red', isError && 'bg-red')   // conditional classes
cn('p-4', { 'hidden': !visible })     // object syntax

Conversion utilities

toMW / toMWh

@tetherto/mdk-react-devkit/core

Convert watts to megawatts.

import { toMW, toMWh } from '@tetherto/mdk-react-devkit/core'

toMW(1000000)   // 1
toMWh(1000000)  // 1

toPHS

@tetherto/mdk-react-devkit/core

Convert raw hashrate to PH/s.

import { toPHS } from '@tetherto/mdk-react-devkit/core'

toPHS(1000000000000000)  // 1

convertMpaToBar

@tetherto/mdk-react-devkit/core

Convert pressure units.

import { convertMpaToBar } from '@tetherto/mdk-react-devkit/core'

convertMpaToBar(0.1)  // 1

unitToKilo

@tetherto/mdk-react-devkit/core

Convert to kilo units.

import { unitToKilo } from '@tetherto/mdk-react-devkit/core'

unitToKilo(1000)  // 1

Number utilities

percentage

@tetherto/mdk-react-devkit/core

Calculate percentage.

import { percentage } from '@tetherto/mdk-react-devkit/core'

percentage(25, 100)  // 25
percentage(1, 4)     // 25

getPercentChange

@tetherto/mdk-react-devkit/core

Calculate percentage change.

import { getPercentChange } from '@tetherto/mdk-react-devkit/core'

getPercentChange(110, 100)  // 10
getPercentChange(90, 100)   // -10

convertUnits

@tetherto/mdk-react-devkit/core

Convert between SI-prefix units.

import { convertUnits } from '@tetherto/mdk-react-devkit/core'

convertUnits(1, 'k', 'M')           // 0.001
convertUnits(1000, 'decimal', 'k')  // 1

safeNumber

@tetherto/mdk-react-devkit/core

Safely convert to number.

import { safeNumber } from '@tetherto/mdk-react-devkit/core'

safeNumber('123')      // 123
safeNumber('invalid')  // 0
safeNumber(null)       // 0

String utilities

toTitleCase

@tetherto/mdk-react-devkit/core

Convert string to Title Case.

import { toTitleCase } from '@tetherto/mdk-react-devkit/core'

toTitleCase('hello world')  // "Hello World"

formatMacAddress

@tetherto/mdk-react-devkit/core

Format MAC addresses.

import { formatMacAddress } from '@tetherto/mdk-react-devkit/core'

formatMacAddress('aa:bb:cc:dd:ee:ff')  // "AA:BB:CC:DD:EE:FF"

safeString

@tetherto/mdk-react-devkit/core

Safely convert to string.

import { safeString } from '@tetherto/mdk-react-devkit/core'

safeString(123)    // "123"
safeString(null)   // ""

Time utilities

secondsToMs

@tetherto/mdk-react-devkit/core

Convert seconds to milliseconds.

import { secondsToMs } from '@tetherto/mdk-react-devkit/core'

secondsToMs(60)  // 60000

breakTimeIntoIntervals

@tetherto/mdk-react-devkit/core

Split [start, end] into consecutive windows of intervalMs. Re-exported from @tetherto/mdk-ui-core.

import { breakTimeIntoIntervals } from '@tetherto/mdk-react-devkit/core'

breakTimeIntoIntervals(start, end, 3_600_000)  // Array of 1-hour { start, end } windows

timeRangeWalker

@tetherto/mdk-react-devkit/core

Generator for iterating through time ranges.

import { timeRangeWalker } from '@tetherto/mdk-react-devkit/core'

for (const interval of timeRangeWalker(start, end, duration)) {
  // Process each interval
}

Color utilities

hexToRgba

@tetherto/mdk-react-devkit/core

Convert hex color to rgba.

import { hexToRgba } from '@tetherto/mdk-react-devkit/core'

hexToRgba('#72F59E', 0.5)  // "rgba(114, 245, 158, 0.5)"

Array utilities

getNestedValue

@tetherto/mdk-react-devkit/core

Get nested value by dot-path.

import { getNestedValue } from '@tetherto/mdk-react-devkit/core'

getNestedValue({ a: { b: 1 } }, 'a.b')  // 1

getWeightedAverage

@tetherto/mdk-react-devkit/core

Calculate weighted average.

import { getWeightedAverage } from '@tetherto/mdk-react-devkit/core'

getWeightedAverage(items, 'value', 'weight')

circularArrayAccess

@tetherto/mdk-react-devkit/core

Create infinite cycling generator.

import { circularArrayAccess } from '@tetherto/mdk-react-devkit/core'

const colors = circularArrayAccess(['red', 'green', 'blue'])
colors.next().value  // 'red'
colors.next().value  // 'green'
colors.next().value  // 'blue'
colors.next().value  // 'red' (cycles)

Foundation utilities

Helpers exported by @tetherto/mdk-react-devkit/foundation for filtering, formatting, validating, parsing, and exporting settings data.

Prerequisites

Import

@tetherto/mdk-react-devkit/foundation
import {
  filterUsers,
  formatRoleLabel,
  formatLastActive,
  validateSettingsJson,
  parseSettingsFile,
  exportSettingsToFile,
} from '@tetherto/mdk-react-devkit/foundation'

Settings utilities

filterUsers

@tetherto/mdk-react-devkit/foundation

Filter a SettingsUser[] list by email substring (case-insensitive) and exact role match. Used by the user-management table search.

import { filterUsers } from '@tetherto/mdk-react-devkit/foundation'

filterUsers({
  users,
  email: 'alice',   // partial, case-insensitive match on user.email
  role: 'admin',    // exact match on user.role; pass null to skip
})
ParameterTypeDescription
usersSettingsUser[]Source list
emailstring | null | undefinedSubstring filter on email (case-insensitive). Skipped when falsy.
rolestring | null | undefinedExact role match. Skipped when falsy.

formatRoleLabel

@tetherto/mdk-react-devkit/foundation

Convert a snake case role identifier to a human-readable Title Case label.

import { formatRoleLabel } from '@tetherto/mdk-react-devkit/foundation'

formatRoleLabel('site_manager')           // "Site Manager"
formatRoleLabel('reporting_tool_manager') // "Reporting Tool Manager"
formatRoleLabel('admin')                  // "Admin"

formatLastActive

@tetherto/mdk-react-devkit/foundation

Format a timestamp string as MM/DD/YYYY - HH:MM. Returns '-' when the input is missing or invalid.

import { formatLastActive } from '@tetherto/mdk-react-devkit/foundation'

formatLastActive('2025-01-15T14:30:00Z')  // "01/15/2025 - 14:30"
formatLastActive(undefined)               // "-"
formatLastActive('not-a-date')            // "-"

validateSettingsJson

@tetherto/mdk-react-devkit/foundation

Type-guard that checks whether an unknown value is a valid SettingsExportData. Returns true if the value is an object that contains at least one of headerControls, featureFlags, or timestamp.

import { validateSettingsJson } from '@tetherto/mdk-react-devkit/foundation'

if (validateSettingsJson(parsed)) {
  // parsed is now narrowed to SettingsExportData
}

parseSettingsFile

@tetherto/mdk-react-devkit/foundation

Read a File containing settings JSON, validate it, and resolve to SettingsExportData. Rejects on invalid JSON, an invalid format, or a file read error.

import { parseSettingsFile } from '@tetherto/mdk-react-devkit/foundation'

try {
  const settings = await parseSettingsFile(file)
  applySettings(settings)
} catch (err) {
  notifyError('Could not import settings', err.message)
}
ThrowsReason
`Invalid settings file format. Please ensure the file is a valid ${WEBAPP_NAME} settings export.`The JSON parsed but didn't match the SettingsExportData shape. The literal interpolates WEBAPP_NAME.
Failed to parse JSON file. Please ensure the file is valid JSON.The file contents weren't valid JSON.
Failed to read file.The browser couldn't read the file.

exportSettingsToFile

@tetherto/mdk-react-devkit/foundation

Serialize SettingsExportData to JSON, package it as a downloadable Blob, and trigger a browser download. Returns the generated filename (e.g., mdk-settings-2025-01-15T14-30-00-000Z.json). The filename prefix is fixed in the foundation kit and does not interpolate WEBAPP_NAME.

import { exportSettingsToFile } from '@tetherto/mdk-react-devkit/foundation'

const filename = exportSettingsToFile({
  headerControls: { poolMiners: true, consumption: false },
  featureFlags: { betaCharts: true },
  timestamp: new Date().toISOString(),
  version: '1.0.0',
})

UI core alert utilities

@tetherto/mdk-ui-core ships two utility modules for the Alerts feature: query-parameter builders (alert-queries) and time-range chunking helpers (historical-log-chunks). These are the source consumed directly by the useCurrentAlertDevices and useHistoricalAlerts adapter hooks.

Prerequisites

Import

@tetherto/mdk-ui-core
import {
  ONE_DAY_MS,
  DEFAULT_HISTORICAL_WINDOW_MS,
  getDefaultHistoricalAlertsRange,
  buildCurrentAlertDevicesParams,
  buildHistoricalAlertsParams,
  breakTimeIntoIntervals,
  mergeAlertsByUuid,
  fetchHistoricalAlertsInChunks,
} from '@tetherto/mdk-ui-core'

Alert query builders

ONE_DAY_MS

@tetherto/mdk-ui-core

One day in milliseconds (86_400_000). The fetch-window size used by the historical-alerts data path.

import { ONE_DAY_MS } from '@tetherto/mdk-ui-core'

ONE_DAY_MS  // 86400000

DEFAULT_HISTORICAL_WINDOW_MS

@tetherto/mdk-ui-core

Default historical-alerts look-back window — 14 days (14 * ONE_DAY_MS). Matches the devkit <Alerts> feature default; wider ranges fan out into more 24-hour requests.

import { DEFAULT_HISTORICAL_WINDOW_MS } from '@tetherto/mdk-ui-core'

DEFAULT_HISTORICAL_WINDOW_MS  // 1209600000 (14 days in ms)

getDefaultHistoricalAlertsRange

@tetherto/mdk-ui-core

Returns the default historical-alerts range: the last DEFAULT_HISTORICAL_WINDOW_MS ending now. Used by the devkit <Alerts> feature and the shell Alerts page to seed their range state. Pass now to fix the upper bound in tests.

import { getDefaultHistoricalAlertsRange } from '@tetherto/mdk-ui-core'

const range = getDefaultHistoricalAlertsRange()
// { start: <14 days ago>, end: <now> }

// Injectable for tests
getDefaultHistoricalAlertsRange(1_700_000_000_000)
// { start: 1_700_000_000_000 - DEFAULT_HISTORICAL_WINDOW_MS, end: 1_700_000_000_000 }
ParameterStatusTypeDefaultDescription
nowOptionalnumberDate.now()Upper bound of the window (ms epoch).

buildCurrentAlertDevicesParams

@tetherto/mdk-ui-core

Builds the list-things query params for the current-alerts table: every device that currently carries one or more alerts, with a 1 000 device limit. Consumed by useCurrentAlertDevices.

filterTags widen the server-side selector so the fetch narrows with the active search chips (ip-, sn-, mac-, firmware-) instead of relying on client-side filtering alone.

import { buildCurrentAlertDevicesParams } from '@tetherto/mdk-ui-core'

// All devices with active alerts
const params = buildCurrentAlertDevicesParams()

// Narrowed to devices matching the active search chips
const filtered = buildCurrentAlertDevicesParams(['ip-192.168.1.1', 'sn-ABC123'])
ParameterStatusTypeDefaultDescription
filterTagsOptionalstring[][]Active alert search chips to narrow the selector server-side.

buildHistoricalAlertsParams

@tetherto/mdk-ui-core

Builds the history-log query params for a single alerts window (logType: 'alerts'). Called once per 24-hour sub-window by the chunked fetch.

import { buildHistoricalAlertsParams } from '@tetherto/mdk-ui-core'

const params = buildHistoricalAlertsParams({ start: range.start, end: range.end })
// { logType: 'alerts', start: ..., end: ... }
ParameterStatusTypeDefaultDescription
rangeRequiredHistoricalAlertsRangenone{ start: number; end: number } — ms epoch bounds for the window.

Historical alert chunking

breakTimeIntoIntervals

@tetherto/mdk-ui-core

Splits [start, end] into consecutive windows of intervalMs (default ONE_DAY_MS); the final window is clamped to end. Returns an empty array for an empty or inverted range.

import { breakTimeIntoIntervals, ONE_DAY_MS } from '@tetherto/mdk-ui-core'

// 24-hour windows (default)
const windows = breakTimeIntoIntervals(start, end)

// Custom window size
const hourly = breakTimeIntoIntervals(start, end, 3_600_000)
ParameterStatusTypeDefaultDescription
startRequirednumbernoneRange start (ms epoch).
endRequirednumbernoneRange end (ms epoch).
intervalMsOptionalnumberONE_DAY_MSWindow size in ms.

Returns TimeInterval[] where each element is { start: number; end: number }.

mergeAlertsByUuid

@tetherto/mdk-ui-core

Concatenates next onto prev, replacing any row that shares a uuid (later windows win) and appending the rest. Rows without a uuid are always appended.

import { mergeAlertsByUuid } from '@tetherto/mdk-ui-core'

const merged = mergeAlertsByUuid(previousAlerts, newAlerts)
ParameterStatusTypeDefaultDescription
prevRequiredT[]noneExisting alert rows.
nextRequiredT[]noneRows from the latest window fetch.

Returns T[] where T extends { uuid?: string }.

fetchHistoricalAlertsInChunks

@tetherto/mdk-ui-core

Fetches a historical-alerts range as successive 24-hour windows (oldest to newest), merging results by uuid. Individual window failures are swallowed so one bad request does not drop the whole range. The loop bails out early when options.signal is aborted.

import {
  fetchHistoricalAlertsInChunks,
  buildHistoricalAlertsParams,
} from '@tetherto/mdk-ui-core'

const alerts = await fetchHistoricalAlertsInChunks(
  { start: range.start, end: range.end },
  async (window) => {
    const result = await myApi.historyLog(buildHistoricalAlertsParams(window))
    return result.data
  },
  { signal: abortController.signal },
)
ParameterStatusTypeDefaultDescription
rangeRequired{ start: number; end: number }noneFull date range to paginate over (ms epoch).
fetchWindowRequiredfunctionnoneCalled once per 24-hour window, oldest first. Receives a TimeInterval and returns Promise<T[]>.
options.intervalMsOptionalnumberONE_DAY_MSWindow size in ms.
options.signalOptionalAbortSignalnoneChecked between windows — aborts the loop early on range changes.

On this page