Helpers#

Utility functions available in @buntok/core.

Password Hashing#

FunctionDescription
hashPassword(password)Hash password using argon2id (via Bun.password)
verifyPassword(password, hash)Verify password (supports argon2id + legacy scrypt + PBKDF2)
typescript
import { hashPassword, verifyPassword } from "@buntok/core/helpers";

// Hash
const hash = await hashPassword("my-password");

// Verify
const isValid = await verifyPassword("my-password", hash);
Info
Uses argon2id via Bun.password (native, Zig-based, 2-10x faster than node:crypto). Backward compatible with legacy scrypt and PBKDF2 hashes.

Timezone Helpers#

FunctionDescription
parseTime(time, tz)Parse time string in timezone
formatInTimezone(date, tz, format)Format date in timezone
toTimezoneParts(date, tz)Get timezone parts
nowInTimezone(tz)Get current time in timezone
getTimezoneOffset(tz)Get offset in minutes (negative = ahead of UTC, e.g. Jakarta -420)
getTimezoneOffsetString(tz)Get offset as string (+07:00)
toISOWithTimezone(date, tz)ISO string with offset
groupByTimezone(items, field, tz, groupBy)Group by hour/day/month/year
getGroupLabels(groupBy)Get group labels
formatGroupLabel(label, groupBy, tz)Format group label
isValidTimezone(tz)Check if timezone is valid
typescript
import {
  parseTime, formatInTimezone, toTimezoneParts,
  nowInTimezone, getTimezoneOffset, getTimezoneOffsetString, isValidTimezone,
  groupByTimezone, getGroupLabels, formatGroupLabel, toISOWithTimezone,
} from "@buntok/core/helpers";

const date = parseTime("14:30", "Asia/Jakarta");
const formatted = formatInTimezone(new Date(), "Asia/Jakarta", "short"); // "short"|"default"|"full"
const offset = getTimezoneOffset("Asia/Jakarta"); // -420 (UTC+7, negative = ahead)
const offsetStr = getTimezoneOffsetString("Asia/Jakarta"); // "+07:00"
isValidTimezone("Asia/Jakarta"); // true
// Group: groupByTimezone(items, "createdAt", "Asia/Jakarta", "day") // GroupByKey "hour"|"day"|"month"|"year"
toISOWithTimezone(new Date(), "Asia/Jakarta"); // "2024-01-15T17:30:00+07:00"

Async Helpers#

FunctionDescription
delay(ms)Promise-based sleep
retry(fn, options?)Retry with configurable backoff
typescript
import { delay, retry } from "@buntok/core/helpers";

// Promise-based sleep
await delay(1000); // 1 second

// Retry with exponential backoff
const data = await retry(
  () => fetch("https://api.example.com/data").then(r => r.json()),
  { retries: 3, delay: 1000, backoff: "exponential" }
);

// Retry with custom error filter
await retry(
  () => db.query("SELECT ..."),
  {
    retries: 5,
    delay: 500,
    backoff: "fixed",
    onError: (err, attempt) => {
      // Only retry on connection errors
      return err instanceof ConnectionError;
    },
  }
);
OptionTypeDefaultDescription
retriesnumber3Max retry attempts
delaynumber1000Base delay in ms
backoff"fixed" | "exponential""exponential"Backoff strategy
onError(err, attempt) => booleanalways retryDecide if error is retryable
Info
delay is a simple Promise-based sleep. retry wraps an async function with configurable retries, backoff, and error filtering.

Error Helpers (asyncHandler)#

Function / ClassDescription
asyncHandler(handler)Wrap handler, auto-catch → error response
HttpErrorBase error class
BadRequestError (400)Invalid request
UnauthorizedError (401)Auth missing
ForbiddenError (403)No access
NotFoundError (404)Not found
ConflictError (409)Conflict
UnprocessableEntityError (422)Business validation
TooManyRequestsError (429)Rate limit
InternalServerError (500)Server error
ServiceUnavailableError (503)Service unavailable
typescript
import { asyncHandler, NotFoundError } from "@buntok/core/helpers";

app.get("/users/:id", asyncHandler(async (ctx) => {
  const user = await findUser(ctx.params.id);
  if (!user) throw new NotFoundError("User not found");
  return ctx.json(user);
}));
Info
See Error Handling for full details.

File Upload#

typescript
import { handleUploads, LocalDiskStorage } from "@buntok/core/upload";

const result = await handleUploads(ctx, {
  storage: new LocalDiskStorage("./uploads"),
  maxFileSize: 5 * 1024 * 1024,
  fields: {
    avatar: { required: true, allowedMimeTypes: ["image/png"] },
  },
});

result.fields.avatar // UploadedFile | ImageUploadedFile (if outputFormat set)
result.files         // (UploadedFile | ImageUploadedFile)[]
Info
See the Upload page for full documentation including storage drivers, custom filenames, and middleware.

Crypto Helpers#

FunctionDescription
hash(data, algorithm?) - SYNCHash data (default: SHA-256, Bun.CryptoHasher)
sha256(data) - SYNCSHA-256 hash
sha512(data) - SYNCSHA-512 hash
md5(data) - asyncMD5 hash (pure JS)
hmac(data, key, algorithm?) - asyncHMAC hash (WebCrypto)
hashVerify(data, expected, algorithm?) - asyncVerify hash matches
randomBytes(length)Random bytes as Uint8Array
randomHex(length)Random hex string
randomAlphaNumeric(length)Random alphanumeric string
randomToken(length?)Random URL-safe token
encrypt(data, key, iv?) → {ciphertext, iv}AES-256-GCM encrypt (iv hex)
decrypt(ciphertext, key, iv)AES-256-GCM decrypt
typescript
import { hash, sha256, md5, randomHex, randomToken, encrypt, decrypt } from "@buntok/core/helpers";

// hash/sha256/sha512 are SYNC (Bun.CryptoHasher)
const h = hash("hello world"); // default SHA-256, or hash("data", "SHA-512")
const h2 = sha256("hello world"); // sync
const md5Hash = await md5("hello world"); // async pure JS
const hex = randomHex(16); // "a1b2c3..."
const token = randomToken(); // "dGhpcyBpcyBh..."
const { ciphertext, iv } = await encrypt("secret", key); // AES-256-GCM, iv is hex
const decrypted = await decrypt(ciphertext, key, iv);

Date Helpers#

FunctionDescription
formatDate(date)Format date as ISO string
timeAgo(date)Human-readable relative time
daysBetween(date1, date2)Days between two dates
addDays(date, days)Add days to a date
isBefore(date1, date2)Check if date1 is before date2
isAfter(date1, date2)Check if date1 is after date2
startOfDay(date)Get start of day
endOfDay(date)Get end of day
formatDuration(seconds)Format seconds as human-readable
typescript
import { timeAgo, addDays, formatDuration, startOfDay } from "@buntok/core/helpers";

timeAgo(new Date("2024-01-01")); // "3 months ago"
const tomorrow = addDays(new Date(), 1);
formatDuration(3661); // "1h 1m 1s"
startOfDay(new Date()); // 00:00:00 today
Need more?
For the full date-fns surface - format & parse tokens, distances, intervals, 396 fp variants, and 95 locales - use @buntok/core/date. See Date Library.

String Helpers#

FunctionDescription
slugify(str)URL-friendly slug
truncate(str, max, suffix?)Truncate with ellipsis
capitalize(str)Capitalize first letter
camelCase(str)Convert to camelCase
snakeCase(str)Convert to snake_case
kebabCase(str)Convert to kebab-case
typescript
import { slugify, truncate, camelCase, kebabCase } from "@buntok/core/helpers";

slugify("Hello World!"); // "hello-world"
truncate("Long text here", 10); // "Long te..."
camelCase("hello_world"); // "helloWorld"
kebabCase("helloWorld"); // "hello-world"

Number Helpers#

FunctionDescription
clamp(value, min, max)Clamp number to range
random(min, max)Random integer in range
randomFloat(min, max)Random float in range
formatNumber(n, locale?)Format number with locale
formatBytes(bytes)Format bytes as human-readable
formatCurrency(amount, currency, locale?)Format as currency
typescript
import { clamp, formatBytes, formatCurrency } from "@buntok/core/helpers";

clamp(15, 0, 10); // 10
formatBytes(1536); // "1.5 KB"
formatCurrency(25000, "IDR"); // "Rp 25.000"

Object Helpers#

FunctionDescription
pick(obj, keys)Pick specified keys
omit(obj, keys)Omit specified keys
groupBy(arr, key)Group array by key
uniq(arr)Remove duplicates
flatten(arr, depth?)Flatten nested array
chunk(arr, size)Split array into chunks
deepMerge(target, ...sources)Deep merge objects
flattenObject(obj, sep?)Flatten nested object
typescript
import { pick, omit, groupBy, uniq, deepMerge } from "@buntok/core/helpers";

pick({ a: 1, b: 2, c: 3 }, ["a", "c"]); // { a: 1, c: 3 }
omit({ a: 1, b: 2 }, ["b"]); // { a: 1 }
groupBy([{ type: "a" }, { type: "b" }, { type: "a" }], "type");
// { a: [...], b: [...] }
uniq([1, 2, 2, 3]); // [1, 2, 3]
deepMerge({ a: 1 }, { b: 2 }); // { a: 1, b: 2 }

Network Helpers#

FunctionDescription
getClientIP(request)Extract client IP from request
isPrivateIP(ip)Check if IP is private/local
parseUserAgent(request)Parse User-Agent header
typescript
import { getClientIP, isPrivateIP, parseUserAgent } from "@buntok/core/helpers";

const ip = getClientIP(request);
isPrivateIP("192.168.1.1"); // true
const ua = parseUserAgent(request);
// { browser: "Chrome", os: "Windows", device: "desktop" }
FunctionDescription
getCookie(request, name)Get cookie from Request
getCookies(request)Get all cookies from Request
setCookie(response, name, value, options?)Set cookie on Response
deleteCookie(response, name, options?)Delete cookie on Response
parseCookies(cookieHeader)Parse Cookie header string
serializeCookie(name, value, options?)Serialize cookie to string
typescript
import { setCookie, deleteCookie, getCookie } from "@buntok/core/helpers";

// Set cookie on response
const response = ctx.json({ ok: true });
return setCookie(response, "token", "abc123", {
  httpOnly: true,
  secure: true,
  sameSite: "lax",
  path: "/",
  maxAge: 86400,
});

// Delete cookie
return deleteCookie(response, "token", { path: "/" });

ID Generators#

FunctionDescription
nanoid(size?)URL-safe unique ID (default: 21 chars)
ulid()Sortable unique ID (时间排序)
generateCode(prefix?, counter?)Generate sequential code (ORD-001)
resetCounter(prefix?)Reset counter for generateCode
typescript
import { nanoid, ulid } from "@buntok/core/helpers";

nanoid(); // "V1StGXR8_Z5jdHi6B-myT"
ulid(); // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
generateCode("ORD"); // "ORD-001"
generateCode("ORD"); // "ORD-002"