npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

coastal

v0.2.1

Published

TypeScript utility library providing common functions.

Readme

coastal

TypeScript utility library providing common mathematical and array manipulation functions.

Installation

pnpm add coastal

Usage

Mathematical Functions

adjustAngle

Adjusts two angles to minimize interpolation distance.

import { adjustAngle } from 'coastal'

adjustAngle(350, 10) // [350, 10] - no adjustment needed
adjustAngle(10, 350) // [370, 350] - adjusts first angle

lerp

Linear interpolation between two values.

import { lerp } from 'coastal'

lerp(0, 100, 0.5) // 50
lerp(0, 100, 0.25) // 25
lerp(-10, 10, 0.5) // 0

lerpAngle

Linear interpolation between two angles, taking the shortest path.

import { lerpAngle } from 'coastal'

lerpAngle(350, 10, 0.5) // 0 - takes shortest path across 0°
lerpAngle(90, 270, 0.5) // 180

lerpArray

Linear interpolation between two arrays element-wise.

import { lerpArray } from 'coastal'

lerpArray([0, 0], [2, 4], 0.5) // [1, 2]
lerpArray([1, 2, 3], [4, 5], 0.5) // [2.5, 3.5] - uses minimum length
lerpArray([], [1, 2], 0.5) // [] - empty array when either input is empty

Edge cases:

  • Empty arrays: Returns empty array when either input array is empty
  • Different lengths: Uses minimum length of the two input arrays

normalize

Normalizes an array of numbers so they sum to 1.

import { normalize } from 'coastal'

normalize([2, 2]) // [0.5, 0.5]
normalize([1, 2, 3]) // [0.16667, 0.33333, 0.5]
normalize([]) // [] - empty array
normalize([1, -1]) // [Infinity, -Infinity] - zero sum case

Edge cases:

  • Empty array: Returns empty array
  • Zero sum: Returns array with Infinity/-Infinity values (division by zero)
  • All zeros: Returns array with NaN values

normalizeAngle

Normalizes an angle to the range [0, 360).

import { normalizeAngle } from 'coastal'

normalizeAngle(450) // 90
normalizeAngle(-90) // 270
normalizeAngle(360) // 0

sum

Calculates the sum of all numbers in an array.

import { sum } from 'coastal'

sum([1, 2, 3, 4]) // 10
sum([]) // 0

szudzik

Szudzik pairing function - maps two non-negative integers to a unique non-negative integer.

import { szudzik } from 'coastal'

szudzik(1, 1) // 3
szudzik(5, 3) // 33
szudzik(-1, 0) // -1 - deterministic but not bijective for negative inputs
szudzik(1.5, 0) // 3.75 - deterministic but not bijective for non-integers

Edge cases:

  • Negative inputs: Produces deterministic results but pairing is not guaranteed to be unique or reversible
  • Non-integer inputs: Produces deterministic results but pairing is not guaranteed to be unique or reversible
  • Intended use: Non-negative integers for mathematically correct bijective pairing

Array Functions

clamp

Constrains a number to fall within specified bounds.

import { clamp } from 'coastal'

clamp(15, 0, 10) // 10
clamp(-5, 0, 10) // 0
clamp(5, 0, 10) // 5

remove

Removes elements from an array in-place where the predicate returns true.

import { remove } from 'coastal'

const numbers = [1, 2, 3, 4, 5]
remove(numbers, (x) => x % 2 === 0) // removes even numbers
console.log(numbers) // [1, 3, 5]

// Predicate receives value, index, and array
const items = ['a', 'b', 'c', 'd']
remove(items, (value, index) => index % 2 === 0) // removes elements at even indices
console.log(items) // ['b', 'd']

upsert

Updates an existing array element or inserts a new one based on a predicate match.

import { upsert } from 'coastal'

// Insert when no match found
const users = [{ id: 1, name: 'Alice' }]
upsert(users, { id: 2, name: 'Bob' }, (user) => user.id === 2)
console.log(users) // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

// Update when match found
const products = [{ id: 1, price: 100 }]
upsert(products, { id: 1, price: 150 }, (product) => product.id === 1)
console.log(products) // [{ id: 1, price: 150 }]

// Works with primitives
const numbers = [1, 2, 3]
upsert(numbers, 99, (n) => n > 5) // no match, appends
console.log(numbers) // [1, 2, 3, 99]

const tags = ['urgent', 'bug']
upsert(tags, 'fixed', (tag) => tag === 'bug') // match found, replaces
console.log(tags) // ['urgent', 'fixed']

Behavior:

  • Inserts at end of array when predicate finds no match
  • Replaces first matching element when predicate finds a match
  • Mutates the original array

mapChunkBy

Maps over an array using dynamically-sized chunks, where each chunk size is determined by examining the current element and its position. Similar to Array.map() but operates on variable-length chunks instead of individual elements.

import { mapChunkBy } from 'coastal'

// Fixed chunk size
const numbers = [1, 2, 3, 4, 5, 6]
mapChunkBy(
  numbers,
  () => 2,
  (chunk, index) => ({ index, sum: chunk.reduce((a, b) => a + b, 0) }),
)
// [{ index: 0, sum: 3 }, { index: 2, sum: 7 }, { index: 4, sum: 11 }]

// Dynamic sizing based on content
const words = ['hi', 'hello', 'a', 'world', 'test']
mapChunkBy(
  words,
  (word) => (word.length > 3 ? 2 : 1),
  (chunk, index) => ({ startIndex: index, text: chunk.join(' ') }),
)
// [{ startIndex: 0, text: 'hi' }, { startIndex: 1, text: 'hello a' }, { startIndex: 3, text: 'world test' }]

// Text processing with sentence detection
const words = ['Hello', 'world!', 'How', 'are', 'you?', 'Fine.']
mapChunkBy(
  words,
  (_, index) => {
    // Take words until punctuation or max 3 words
    let count = 1
    for (let i = index; i < Math.min(index + 3, words.length); i++) {
      if (words[i].includes('!') || words[i].includes('?') || words[i].includes('.')) {
        return i - index + 1
      }
      if (i > index) count++
    }
    return count
  },
  (chunk) => chunk.join(' '),
)
// ['Hello world!', 'How are you?', 'Fine.']

Edge cases:

  • Empty array: Returns empty array
  • Chunk size exceeds remaining elements: Uses all remaining elements
  • Invalid chunk size: Throws Error for non-positive integers, decimals, NaN, or Infinity
  • Source array is not modified

Data Structures

DirectAddressTable

A direct address table providing O(1) lookup time for non-negative integer keys. The table is immutable after construction.

import { DirectAddressTable } from 'coastal'

// Basic usage
const table = new DirectAddressTable([0, 2, 5], ['a', 'b', 'c'])
table.get(0) // 'a'
table.get(1) // undefined
table.get(2) // 'b'
table.get(5) // 'c'

// Duplicate keys - last value wins
const table2 = new DirectAddressTable([1, 2, 1], ['first', 'second', 'third'])
table2.get(1) // 'third'

// Invalid keys throw errors during construction
try {
  new DirectAddressTable([-1, 0], ['a', 'b']) // throws Error
} catch (e) {
  console.log(e.message) // "Invalid key: -1. Keys must be non-negative integers"
}

try {
  new DirectAddressTable([1.5, 2], ['a', 'b']) // throws Error
} catch (e) {
  console.log(e.message) // "Invalid key: 1.5. Keys must be non-negative integers"
}

Performance characteristics:

  • Time complexity: O(1) lookup
  • Space complexity: O(max_key) where max_key is the largest key value
  • Memory usage scales with the largest key, not the number of stored values

Edge cases:

  • Empty key arrays: Throws RangeError
  • Mismatched array lengths: Keys without corresponding values are assigned undefined
  • Invalid keys: Throws Error for negative numbers or non-integers
  • Large key values: Uses memory proportional to the largest key value
  • Duplicate keys: Last value overwrites previous values