{"version":3,"file":"lib-BRm76eaZ.mjs","names":[],"sources":["../src/lib/Format.ts","../src/lib/Timers.ts","../src/lib/Queue.ts","../src/lib/Conversion.ts"],"sourcesContent":["import { formatDate, formatDistanceToNow } from 'date-fns';\nimport pc from 'picocolors';\nimport Pluralize from 'pluralize';\n\nexport type TimestampFormat =\n    | 'date-only'   // January 1 2026\n    | 'with-hour'   // January 1 2026 • 00:00 AM\n    | 'with-second' // January 1 2026 • 00:00:00 AM\n    | 'relative'    // 1 year ago\n\nexport type TimestampFormatOptions = {\n    format?: TimestampFormat;\n    \n    /**\n     * Omit suffixes from relative timestamps\n     * @default false\n     */\n    noSuffix?: boolean;\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function formatTimestamp(\n    time: number | Date,\n    { format = 'with-hour', noSuffix }: TimestampFormatOptions,\n) {\n    if (format === 'with-hour') {\n        return formatDate(time, 'MMMM d yyyy • hh:mm a');\n    }\n    if (format === 'with-second') {\n        return formatDate(time, 'MMMM d yyyy • hh:mm:ss a');\n    }\n    if (format === 'relative') {\n        return formatDistanceToNow(time, { addSuffix: noSuffix !== true });\n    }\n    return formatDate(time, 'MMMM d yyyy');\n}\n\n/**\n * Format an environment variable that should result in either true or false.\n *\n * @example\n * FOO=0 // false\n * FOO=false // false\n * FOO=abc // true\n */\n// @__NO_SIDE_EFFECTS__\nexport function getEnvironmentFlag(variable: keyof typeof process.env) {\n    const value = process.env[variable];\n    \n    if (['0', 'false'].includes(`${value}`)) {\n        return false;\n    }\n    if (!value) {\n        return false;\n    }\n    return true;\n}\n\n/**\n * Fetch an entry from the environment variables, throwing an error if it is not found.\n */\n// @__NO_SIDE_EFFECTS__\nexport function requiredEnv<TKey extends EnvironmentVariable>(env: TKey) {\n    const value = process.env[env];\n    if (!value) {\n        throw new Error(`Environment variable \"${env}\" is required!`);\n    }\n    return value;\n}\n\n/**\n * Create an object of the provided environment variables, throwing an error if any are not found.\n * @param environmentVariables\n */\n// @__NO_SIDE_EFFECTS__\nexport function requiredEnvironmentVariables<TVariables extends EnvironmentVariable>(environmentVariables: TVariables[]): {\n    [key in TVariables]: string;\n} {\n    const result: any = {};\n    \n    environmentVariables.forEach((env) => {\n        result[env] = requiredEnv(env);\n    });\n    \n    return result;\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function pluralize(word: string, count: number, inclusive = true) {\n    return Pluralize(word, count, inclusive);\n}\n\n/**\n * Convert an object to a query param string\n * @example\n * objectToUrlParams({ foo: 'bar', hello: 123 }\n *   // \"foo=bar&hello=123\"\n */\n// @__NO_SIDE_EFFECTS__\nexport function objectToUrlParams(object: Record<string, any>, options?: { colorize: boolean }) {\n    const result = Object.entries(object).map(([key, value]) => {\n        const tuple = [key, value];\n        \n        if (options?.colorize) {\n            tuple[0] = pc.blue(pc.italic(key));\n            tuple[1] = pc.green(value);\n        }\n        \n        return tuple.join('=')\n    }).join('&');\n    \n    if (options?.colorize) {\n        return result\n            .replace(/&/g, pc.dim('&'))\n            .replace(/=/g, pc.dim('='))\n    }\n    \n    return result;\n}\n\n/**\n * Take a query string and convert it to an object.\n * @example\n * urlParamsToObject('?hello=world&foo=bar')\n * // -> { hello: 'world', foo: 'bar' }\n * @param query URL query string/parameters\n */\n// @__NO_SIDE_EFFECTS__\nexport function urlParamsToObject(query: string): Record<string, string | null | undefined> {\n    const params = query.replace('?', '').split('&');\n    const entries = params.map((entry) => entry.split('='));\n    return Object.fromEntries(entries);\n}\n\n/**\n * Helper function for parsing env flags to true/false/null based on its string value.\n * Useful for preventing environment flags like DISABLE_FOO=\"false\" from coercing to true.\n * @param key\n */\n// @__NO_SIDE_EFFECTS__\nexport function envFlag(key: keyof NodeJS.ProcessEnv) {\n    const value = process.env[key];\n    \n    if (typeof value === 'undefined') {\n        return null;\n    }\n    \n    if (['0', 'false'].includes(value)) {\n        return false;\n    }\n    \n    \n    if (['1', 'true'].includes(value)) {\n        return true;\n    }\n    \n    console.warn(`Unknown ENV flag: ${key}=${value}`);\n    \n    return null;\n}\n\nexport type EnvironmentVariable = keyof NodeJS.ProcessEnv;","/**\n * Async sleep.\n * @param ms Number of milliseconds before the promise resolves\n */\nexport function sleep(ms: number) {\n    return new Promise<void>(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Works like setInterval, just that it will not permit another callback from being fired until the previous call has\n * resolved.\n * @param callback (Async) function to execute at the provided interval.\n * @param intervalMs Frequency in milliseconds for how often the callback should be fired.\n */\n// @__NO_SIDE_EFFECTS__\nexport function sequentialInterval(\n    callback: SequentialIntervalConfig['callback'],\n    intervalMs: SequentialIntervalConfig['intervalMs'],\n) {\n    return new SequentialInterval({\n        intervalMs,\n        callback,\n    });\n}\n\n/**\n * Sequential and controllable implementation of Node's setInterval function. Prevents interval hooks from being fired\n * until the previous call has completed.\n */\n// @__NO_SIDE_EFFECTS__\nexport class SequentialInterval {\n    protected timer: NodeJS.Timeout | undefined;\n    \n    public get callback() {\n        return this.config.callback;\n    }\n    \n    public get intervalMs() {\n        return this.config.intervalMs;\n    }\n    \n    constructor(public readonly config: SequentialIntervalConfig) {\n        if (config.startImmediately !== false) {\n            this.start();\n        }\n    }\n    \n    protected async run() {\n        await this.callback();\n        this.scheduleRun();\n    }\n    \n    protected scheduleRun() {\n        this.timer = setTimeout(() => this.run(), this.intervalMs);\n    }\n    \n    public start() {\n        this.scheduleRun();\n    }\n    \n    public stop() {\n        if (!this.timer) {\n            return;\n        }\n        clearTimeout(this.timer);\n    }\n}\n\ninterface SequentialIntervalConfig {\n    callback: () => void | Promise<void>;\n    intervalMs: number;\n    startImmediately?: boolean;\n}","interface BatchConfig<T> {\n    interval: number;\n    run: (entries: T[]) => Promise<void>;\n}\n\n/**\n * Create a task scheduler that will trigger at the provided timeout/interval.\n * Used to batch a series of Meilisearch document update requests into a single task\n * to prevent excessive single-task indexing jobs on Meilisearch.\n */\n// @__NO_SIDE_EFFECTS__\nexport function defineBatchableTask<T>(config: BatchConfig<T>) {\n    const data = new Set<T>();\n    let lastAction = 0;\n    \n    return {\n        intervalReached() {\n            return Date.now() - lastAction > config.interval;\n        },\n        async add(entry: T) {\n            data.add(entry);\n            \n            if (!this.intervalReached()) {\n                return;\n            }\n            \n            lastAction = Date.now();\n            const entries = [...data.values()];\n            data.clear();\n            await config.run(entries);\n        }\n    }\n}\n\n","const MILLISECONDS_IN_HOUR = 1000 * 60 * 60;\nconst MILLISECONDS_IN_DAY = MILLISECONDS_IN_HOUR * 24;\n\n// @__NO_SIDE_EFFECTS__\nexport function daysToMilliseconds(days: number) {\n    return Math.round(MILLISECONDS_IN_DAY * days);\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function weeksToMilliseconds(weeks: number) {\n    return daysToMilliseconds(weeks * 7);\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function monthsToMilliseconds(months: number) {\n    return daysToMilliseconds(months * 30);\n}\n\n/**\n * Serialize a currency amount from its lowest denomination to the standard denomination.\n * @example\n * 100 = $1\n * $49.99 = 4999\n */\n// @__NO_SIDE_EFFECTS__\nexport function formatCentsToDollars(amount: number, {\n    currencySymbol = true,\n    currencyCode = '',\n    maxLength = 255,\n    currencyCodePrefix = false,\n} = {}) {\n    let prefix = '';\n    let suffix = '';\n    \n    // todo: handle other currencies\n    if (currencySymbol && currencyCode === 'USD') {\n        prefix = '$';\n    }\n    if (currencyCode) {\n        suffix = ` ${currencyCode}`;\n    }\n    if (currencyCodePrefix) {\n        prefix = `${currencyCode} ${prefix}`;\n    }\n    \n    return `${prefix}${formatCentsToFloat(amount).toString().slice(0, maxLength)}${suffix}`;\n}\n\n/**\n * Take an integer in US cents and convert it to a dollar amount in decimal.\n * @param amount Amount in cents\n * @example\n * formatCentsToFloat(1337) // -> $13.37\n */\n// @__NO_SIDE_EFFECTS__\nexport function formatCentsToFloat(amount: number) {\n    return Math.round((amount / 100) * 100) / 100;\n}"],"mappings":"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"}