# API Reference

Complete reference for all 14 puffy-core modules. Every function is available in both camelCase and snake_case (e.g., `newId` and `new_id`).

## Table of Contents
- [collection](#collection)
- [converter](#converter)
- [crypto](#crypto)
- [date](#date)
- [db](#db)
- [error](#error)
- [func](#func)
- [logger](#logger)
- [math](#math)
- [obj](#obj)
- [string](#string)
- [time](#time)
- [url](#url)
- [validate](#validate)

---

## collection

> ES Modules: `import { batch, uniq, sortBy, seed, headTail, levelUp, flatten, flattenUniq } from 'puffy-core/collection'`
> CommonJS: `const { collection } = require('puffy-core')`

```js
// batch - Breaks array into chunks of specified size
batch([1,2,3,4,5,6,7,8,9,10], 3) // [[1,2,3], [4,5,6], [7,8,9], [10]]

// uniq - Removes duplicates, with optional identity function
uniq([1,1,1,1,2,2]) // [1,2]
uniq([{ name:'Ben' },{ name:'Ben' },{ name:'Jerry' }], x => x.name) // [{ name:'Ben' }, { name:'Jerry' }]

// sortBy - Sorts arrays ascending (default) or descending, with optional mapper
sortBy([2,1,4,3]) // [1,2,3,4]
sortBy([2,1,4,3], 'desc') // [4,3,2,1]
sortBy([{ name:'Nic', age:40 }, { name:'Lin', age:30 }], x => x.age) // [{ name:'Lin', age:30 }, { name:'Nic', age:40 }]
sortBy([{ name:'Nic', age:40 }, { name:'Lin', age:30 }], x => x.age, 'desc') // [{ name:'Nic', age:40 }, { name:'Lin', age:30 }]

// seed - Creates array of specified length filled with undefined
seed(3) // [undefined, undefined, undefined]

// headTail - Splits array into [head, tail] by size
headTail([1,2,3,4,5,6,7,8], 3) // [[1,2,3], [4,5,6,7,8]]

// levelUp - Pads all arrays to the length of the longest
levelUp([1], [2,2], [3,3,3]) // [[1,undefined,undefined], [2,2,undefined], [3,3,3]]

// flatten - Recursively flattens nested arrays
flatten(1,[1,2,3],[4,5,[6,7]],8,9,[6]) // [1,1,2,3,4,5,6,7,8,9,6]

// flattenUniq - Flattens and removes duplicates (primitives only)
flattenUniq(1,[1,2,3],[4,5,[6,7]],8,9,[6]) // [1,2,3,4,5,6,7,8,9]
```

---

## converter

> ES Modules: `import { addZero, nbrToCurrency, s2cCase, c2sCase, objectC2Scase, objectS2Ccase, toNumber, toBoolean, toObj, toArray } from 'puffy-core/converter'`
> CommonJS: `const { converter } = require('puffy-core')`

```js
// addZero - Left-pads number with zeros to specified length
addZero(123, 10) // '0000000123'

// nbrToCurrency - Formats number as currency string
nbrToCurrency(123)          // '$123.00'
nbrToCurrency(123, '$')     // '$123.00'
nbrToCurrency(123, 'dollar')// '$123.00'
nbrToCurrency(123, 'euro')  // '€123.00'
nbrToCurrency(123, 'yen')   // '¥123.00'
nbrToCurrency(123, 'yuan')  // '¥123.00'
nbrToCurrency(123, 'pound') // '£123.00'

// s2cCase / c2sCase - Case conversion for strings
s2cCase('hello_world')  // 'helloWorld'
c2sCase('helloWorld')   // 'hello_world'

// objectC2Scase / objectS2Ccase - Recursively convert object keys
objectC2Scase({ firstName:'Nic', lastName:'Dao' }) // { first_name:'Nic', last_name:'Dao' }
objectS2Ccase({ first_name:'Nic' })                // { firstName:'Nic' }

// capital2cCase / objectCapital2Ccase - Capital case to camelCase
capital2cCase('FirstName') // 'firstName'

// toNumber - Convert to number with optional default
toNumber('123')        // 123
toNumber('abc', 0)     // 0

// toBoolean - Convert to boolean with optional default
toBoolean('true')      // true
toBoolean('yes')       // true
toBoolean(1)           // true

// toObj - Parse JSON string to object with optional default
toObj('{"a":1}')       // { a: 1 }
toObj('invalid', {})   // {}

// toArray - Parse JSON string to array with optional default
toArray('[1,2]')       // [1, 2]
toArray('invalid', []) // []
```

---

## crypto

> ES Modules: `import { encoder, jwtDecode } from 'puffy-core/crypto'`
> CommonJS: `const { crypto } = require('puffy-core')`

```js
// encoder - Curried encoding converter: encoder(fromEncoding)(toEncoding)(data)
// Supported encodings: 'utf8' (default), 'base64', 'hex', 'bin', 'buffer', 'int', 'ascii'

const stringToBase64 = encoder()('base64')
stringToBase64('hello world') // 'aGVsbG8gd29ybGQ='

const base64ToString = encoder('base64')()
base64ToString('aGVsbG8gd29ybGQ=') // 'hello world'

const stringToHex = encoder()('hex')
stringToHex('hello world') // '68656C6C6F20776F726C64'

const base64ToHex = encoder('base64')('hex')
base64ToHex('aGVsbG8gd29ybGQ=') // '68656C6C6F20776F726C64'

// Also supports 'bin' (binary string) and 'buffer' (Node.js Buffer)

// jwtDecode - Decodes JWT token without verification
const { header, payload, signBase64 } = jwtDecode('eyJhbG...')

// With noFail option: returns null instead of throwing on invalid tokens
const data = jwtDecode('invalid-token', { noFail: true }) // null
```

---

## date

> ES Modules: `import { addDays, addMonths, addYears, addHours, addMinutes, addSeconds, formatDate, getTimeDiff, toTz } from 'puffy-core/date'`
> CommonJS: `const { date } = require('puffy-core')`

```js
// Date arithmetic - all accept (date, value) and return new Date
addSeconds(new Date(), 30)
addMinutes(new Date(), 65)
addHours(new Date(), 3)
addDays(new Date(), 4)
addMonths(new Date(), 3)
addYears(new Date(), -10)

// formatDate - Format date with custom patterns
const d = new Date('2021-10-12T13:45:21Z')
formatDate(d)                                            // '2021-10-12'
formatDate(d, { format:'dd-MM-yyyy' })                   // '12-10-2021'
formatDate(d, { format:'dd/MM/yy HH:mm:ss' })           // '12/10/21 13:45:21'
formatDate(d, { format:'The dd{nth} of MMM, yyyy' })     // 'The 12th of October, 2021'

// Format with timezone (IANA timezone names)
formatDate(d, { format:'dd/MM/yy HH:mm:ss', tz:'Australia/Sydney' }) // '13/10/21 00:45:21'
formatDate(d, { format:'dd/MM/yy HH:mm:ss', tz:'local' })           // local time

// toTz - Shift UTC date to timezone-adjusted UTC date
const d2 = new Date('2024-01-23T09:42:57.311Z')
toTz(d2, 'Australia/Sydney').toISOString() // '2024-01-23T20:42:57.311Z'
toTz(d2, 'America/Chicago').toISOString()  // '2024-01-23T03:42:57.311Z'

// getTimeDiff - Difference between two dates
// Units: 'ms', 'millisecond', 's', 'sec', 'second', 'm', 'minute',
//         'h', 'hour', 'd', 'day', 'w', 'week', 'month', 'y', 'year'
getTimeDiff('2021-08-12', new Date('2021-12-12T13:09'), 'day')  // 122.09
getTimeDiff('2021-08-12', new Date('2021-12-12T13:09'), 'hour') // 2930.15
```

---

## db

> ES Modules: `import { newId } from 'puffy-core/db'`
> CommonJS: `const { db } = require('puffy-core')`

Generates monotonically increasing BigInt IDs suitable for database primary keys. The ID structure: `[13-digit epoch ms][2-digit node][4-digit random index]`.

```js
newId()                                      // 1737111960607002926n
newId({ node: 18 })                          // 1737111960611181693n
newId({ node: 18, idx: 9999 })               // 1737111960611189999n
newId({ timestamp: new Date('1985-03-21') }) //  480211200000003307n
```

| Option | Type | Default | Range | Description |
|---|---|---|---|---|
| `timestamp` | Date | `new Date()` | -- | Base timestamp for the ID |
| `node` | number | `0` | 0--99 | Node identifier for distributed systems |
| `idx` | number | random | 0--9999 | Sequence index within the same millisecond |

**Note:** Returns `BigInt`. Use `safeStringify()` from `puffy-core/string` for JSON serialization. See [Data Processing Gotchas](gotchas/data-processing.md#newid-returns-bigint).

---

## error

> ES Modules: `import { catchErrors, wrapErrors, wrapErrorsFn, wrapCustomErrors, mergeErrors, getErrorMetadata, PuffyResponse, required } from 'puffy-core/error'`
> CommonJS: `const { error } = require('puffy-core')`

This is puffy-core's most complex module. For a comprehensive guide, see [Error Handling Guide](error-handling.md).

| Function | Signature | Description |
|---|---|---|
| `catchErrors` | `(msg?, exec) => [errors, data]` | Universal error wrapper for sync/async/promise |
| `wrapErrors` | `(msg, ...errors) => Error` | Creates error with nested error stack |
| `wrapErrorsFn` | `(msg) => (...errors) => Error` | Reusable error wrapper factory |
| `wrapCustomErrors` | `(metadata) => (msg) => Error` | Error factory with attached metadata |
| `mergeErrors` | `(errors) => Error` | Combines error array into single Error |
| `getErrorMetadata` | `(errors) => object` | Extracts merged metadata from errors |
| `required` | `(obj) => void` | Throws if any property is falsy |
| `PuffyResponse` | class extends Array | `[errors, data]` tuple type |

---

## func

> ES Modules: `import { chainAsync, chainSync } from 'puffy-core/func'`
> CommonJS: `const { func } = require('puffy-core')`

Chains functions with automatic error propagation. Each function receives the previous result. Stops on first error.

```js
const [errors, data] = await chainAsync(
    () => Promise.resolve(1),
    previous => Promise.resolve(previous + 1),
    previous => previous + 2
)
// errors = null
// data = { data: [1, 2, 4], value: 4 }

// If a function returns PuffyResponse with errors, chain stops:
const [errors, data] = await chainAsync(
    () => Promise.resolve(1),
    previous => catchErrors(Promise.reject(new Error('Boom')))
)
// errors = [Error("'chainAsync' function failed"), Error('Boom')]
// data = null

// Synchronous version:
const [errors, data] = chainSync(
    () => 1,
    previous => previous + 1,
    previous => previous + 2
)
```

---

## logger

> ES Modules: `import { log } from 'puffy-core/logger'`
> CommonJS: `const { logger } = require('puffy-core')`

Structured JSON logging. Outputs to `console.log` (INFO/WARN) or `console.error` (ERROR/CRITICAL).

```js
log({
    level: 'INFO',    // 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL'
    message: 'Hello world',
    code: '12343',
    data: { hello: 'world' },
    errors: ['Oh no', new Error('Boom')],
    time: 150,        // Auto-sets metric=true, unit='ms'
    opId: 'req-123',  // Operation ID for tracing
    test: false        // Test flag
})
```

**Global metadata:** Set `process.env.LOG_META` to a JSON string to attach metadata to every log entry.

---

## math

> ES Modules: `import { avg, stdDev, median, percentile, getRandomNumber, getRandomNumbers } from 'puffy-core/math'`
> CommonJS: `const { math } = require('puffy-core')`

```js
// Statistical functions - all accept optional mapper function
avg([1,2,3,4])    // 2.5
stdDev([1,2,3,4]) // 1.118033988749895
median([1,2,3,4]) // 2.5

// percentile - Curried: configure once, apply to many arrays
const p95 = percentile(95)
p95([12,45,23,87,13,54,23,12,1,1,23,67,54,34,35,43,27,56]) // 87

// With mapper function
avg([{ v:10 }, { v:20 }], x => x.v) // 15

// Random numbers
getRandomNumber()                            // 0.37... (0-1 float)
getRandomNumber({ start:1000, end:3000 })    // Random int in [1000, 3000)
getRandomNumbers({ start:1000, end:3000, size:5 }) // 5 unique random ints
```

---

## obj

> ES Modules: `import { merge, diff, same, exists, existsAny, existsAll, isEmpty, isObj, getType, mirror, setProperty, getProperty, extractFlattenedJSON } from 'puffy-core/obj'`
> CommonJS: `const { obj } = require('puffy-core')`

```js
// merge - Deep merge objects (later overrides earlier)
merge({ name:'Nic', age:38 }, { address:{ street:'hello' } }, { age:40 })
// { name:'Nic', age:40, address:{ street:'hello' } }

// diff - Returns only changed properties
diff({ a:1, b:2 }, { a:1, b:3, c:4 }) // { b:3, c:4 }

// same - Deep equality (arrays treated as unordered sets)
same({ a:1 }, { a:1 })   // true
same([1,2], [2,1])        // true
same({ a:1 }, { a:2 })   // false

// exists / existsAny / existsAll - Null/undefined checks
exists(0)                  // true
exists(null)               // false
existsAny(null, 0)         // true
existsAll(0, '')           // true

// isEmpty - Checks if object equals '{}'
isEmpty({})                // true
isEmpty({ a:1 })           // false

// isObj - Validates JSON-serializable object
isObj({})                  // true
isObj(new Date())          // false

// getType - Granular type detection
getType([])                // 'array'
getType(new Date())        // 'date'
getType(null)              // null

// setProperty / getProperty - Dot notation and array indexing
setProperty({ name:'Nic' }, 'company.name', 'Neap') // { name:'Nic', company:{ name:'Neap' } }
getProperty({ friends:[{ name:'Peter' }] }, 'friends[0].name') // 'Peter'

// mirror - Match object properties to reference shape
mirror({ a:1, b:2, c:3 }, { a:null, c:null }) // { a:1, c:3 }

// extractFlattenedJSON - Convert flat dot-notation keys to nested object
extractFlattenedJSON({ 'user.name':'Nic', 'user.age':38 })
// { user: { name:'Nic', age:38 } }
```

---

## string

> ES Modules: `import { plural, justifyLeft, safeStringify } from 'puffy-core/string'`
> CommonJS: `const { string } = require('puffy-core')`

```js
// safeStringify - JSON.stringify that handles BigInt
safeStringify({ id: 123n }) // '{"id":"123"}'

// plural - Pluralizes words with special-case handling
plural(1, 'cat')              // 'cat'
plural(2, 'cat')              // 'cats'
plural(2, 'He')               // 'They'
plural(2, 'its')              // 'their'
plural(2, 'Project', 'was')   // 'Projects were'
plural(1, 'She', 'is')        // 'She is'
plural(2, 'She', 'is')        // 'They are'

// justifyLeft - Left-aligns multi-line text
const text = `\t\tHello,\n\t\tThis is a list:\n\t\t- Fruits:`
justifyLeft(text)                          // Strips common leading whitespace
justifyLeft(text, { prefix:'> ' })         // Adds prefix to each line
justifyLeft(text, { anchorLine:0 })        // Uses line 0 indentation as reference
justifyLeft(text, { remove:'\t' })         // Removes specific characters
justifyLeft(text, { skip:0 })              // Skips line 0 from output
justifyLeft(text, { skip:[0,1] })          // Skips lines 0 and 1
```

---

## time

> ES Modules: `import { delay, Timer } from 'puffy-core/time'`
> CommonJS: `const { time } = require('puffy-core')`

```js
// delay - Cancellable async delay
await delay(2000)                           // Wait 2 seconds
await delay([1000, 5000])                   // Random delay between 1-5 seconds
const result = await delay(1000, { hello:'world' }) // Returns custom response after delay

// Cancellation
const d = delay(5000, 'slow')
d.cancel()                                  // Cancels the delay

// Timer - Measure elapsed time
const timer = new Timer()
// ... do work ...
timer.time('second')         // Elapsed seconds (e.g., 1.23)
timer.time('ms')             // Elapsed milliseconds
timer.time('minute')         // Elapsed minutes
timer.time('hour')           // Elapsed hours
timer.time('second', true)   // Get time AND restart timer
timer.reStart()              // Restart timer
```

---

## url

> ES Modules: `import { getUrlParts } from 'puffy-core/url'`
> CommonJS: `const { url } = require('puffy-core')`

Parses URLs using the native `URL` API with additional convenience properties.

```js
getUrlParts('https://localhost:3456/hello/world.html?name=carl&age=40#home')
// {
//   href: 'https://localhost:3456/hello/world.html?name=carl&age=40#home',
//   origin: 'https://localhost:3456',
//   protocol: 'https:',
//   username: '',
//   password: '',
//   host: 'localhost:3456',
//   hostname: 'localhost',
//   port: '3456',
//   pathname: '/hello/world.html',
//   search: '?name=carl&age=40',
//   searchParams: URLSearchParams { 'name' => 'carl', 'age' => '40' },
//   queryParams: { name: 'carl', age: '40' },  // Object version of searchParams
//   hash: '#home',
//   ext: '.html'                                 // File extension from pathname
// }
```

---

## validate

> ES Modules: `import { validateUrl, validateEmail, validateDate, validateSpecialChar, SPECIAL_CHAR } from 'puffy-core/validate'`
> CommonJS: `const { validate } = require('puffy-core')`

```js
// validateUrl - Regex-based URL validation (rejects private IPs)
validateUrl('https://neap.co')    // true
validateUrl('hello')               // false

// validateEmail - RFC-like email validation
validateEmail('nic@neap.co')      // true
validateEmail('nic @neap.co')     // false

// validateDate - Checks if value is a valid Date
validateDate(new Date())           // true
validateDate(new Date('invalid'))  // false
validateDate('not a date', { exception:{ toggle:true, message:'Bad date' } }) // throws

// validateSpecialChar - Checks for presence of special characters
validateSpecialChar('hello!')      // true
validateSpecialChar('hello')       // false

// SPECIAL_CHAR - String containing all recognized special characters
SPECIAL_CHAR // '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
```
