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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wszerad/items

v0.3.0

Published

An immutable collection library for managing entities with built-in selection, filtering, and diffing capabilities.

Readme

@wszerad/items

An immutable collection library for managing entities with built-in selection, filtering, and diffing capabilities.

License: MIT npm version

Features

  • 🔒 Immutable: All operations return new instances without mutating the original
  • 🎯 Type-safe: Full TypeScript support with generic types
  • 🔍 Powerful Selection: Query and filter items with a fluent API
  • 📊 Diff Tracking: Compare collections and track changes
  • Performance: Efficient internal storage using Maps
  • 🎨 Flexible: Custom ID selectors and sorting comparers

Installation

npm install @wszerad/items

Quick Start

import { Items } from '@wszerad/items'

interface User {
  id: number
  name: string
  age: number
}

// Create a collection
const users = new Items<User>([
  { id: 1, name: 'Alice', age: 30 },
  { id: 2, name: 'Bob', age: 25 },
  { id: 3, name: 'Charlie', age: 35 }
])

// Add items
const updated = users.add([
  { id: 4, name: 'David', age: 40 }
])

// Update items
const older = users.update([1, 2], (user) => ({
  ...user!,
  age: user!.age + 1
}))

// Select and filter - returns array
const adults = users.select((s) => 
  s.filter((user) => user.age >= 30)
    .sort((a, b) => a.age - b.age)
)
console.log(adults) // [{ id: 1, name: 'Alice', age: 30 }, { id: 3, name: 'Charlie', age: 35 }]

// Select single item using at() - returns single item or undefined
const oldestUser = users.select((s) => 
  s.sort((a, b) => b.age - a.age).at(0)
)
console.log(oldestUser) // { id: 3, name: 'Charlie', age: 35 }

// Get IDs instead of items
const adultIds = users.selectId((s) => 
  s.filter((user) => user.age >= 30)
)
console.log(adultIds) // [1, 3]

API Reference

Items Class

Constructor

new Items<E>(items?: Iterable<E>, options?: ItemsOptions<E>)

Options:

  • selectId?: (entity: E) => ItemId - Custom ID selector (defaults to entity.id)
  • sortComparer?: false | ((a: E, b: E) => number) - Sorting comparator for maintaining order

Example:

const items = new Items(users, {
  selectId: (user) => user.userId,
  sortComparer: (a, b) => a.name.localeCompare(b.name)
})

Methods

add(items: Iterable<E>): Items<E>

Adds new items to the collection. Items with existing IDs are ignored.

const updated = users.add([
  { id: 4, name: 'David', age: 40 }
])
update(selector: Selector<E>, updater: Updater<E>): Items<E>

Updates selected items with partial data or a function. Can create new items if using a function updater.

// Update with partial data
const updated = users.update(1, { age: 31 })

// Update with function
const updated = users.update([1, 2], (user) => ({
  ...user!,
  age: user!.age + 1
}))

// Update with selector function
const updated = users.update(
  (s) => s.filter((u) => u.age > 25),
  { active: true }
)
merge(items: Iterable<E>): Items<E>

Merges items into the collection. Adds new items and overwrites existing ones.

const merged = users.merge([
  { id: 1, name: 'Alice', age: 31 }, // Updates existing
  { id: 5, name: 'Eve', age: 28 }     // Adds new
])
remove(selector: Selector<E>): Items<E>

Removes selected items from the collection.

// Remove by ID
const removed = users.remove(1)

// Remove by IDs
const removed = users.remove([1, 2])

// Remove by function
const removed = users.remove((s) => s.filter((u) => u.age < 30))
pick(selector: Selector<E>): Items<E>

Returns a new Items instance containing only the selected items.

const picked = users.pick((s) => s.filter((u) => u.age >= 30))
select(selector: Selector<E>): E[] | E | undefined

Selects items and returns them. Returns an array for multiple selections, or a single item (or undefined) when using SingleSelect methods like at() or on().

// Select by ID - returns single item or undefined
const user = items.select(1)

// Select by IDs - returns array
const users = items.select([1, 2])

// Select with function returning Select - returns array
const adults = items.select((s) => s.filter((u) => u.age >= 30))

// Select with function returning SingleSelect - returns single item or undefined
const firstUser = items.select((s) => s.at(0))
const oldest = items.select((s) => s.sort((a, b) => b.age - a.age).at(0))
selectId(selector: Selector<E>): ItemId[] | ItemId | undefined

Selects item IDs instead of items. Returns an array of IDs for multiple selections, or a single ID (or undefined) when using SingleSelect methods.

// Get IDs for filtered items - returns array
const adultIds = items.selectId((s) => s.filter((u) => u.age >= 30))

// Get ID of first item - returns ItemId or undefined
const firstId = items.selectId((s) => s.at(0))

// Get ID of oldest user - returns ItemId or undefined
const oldestId = items.selectId((s) => s.sort((a, b) => b.age - a.age).at(0))
extractId(entity: E): ItemId

Extracts the ID from an entity using the configured ID selector.

const user = { id: 5, name: 'Eve', age: 28 }
const id = items.extractId(user) // Returns: 5

// With custom selector
const products = new Items(items, { 
  selectId: (p) => p.sku 
})
const product = { sku: 'ABC123', name: 'Widget' }
const sku = products.extractId(product) // Returns: 'ABC123'
clear(): Items<E>

Returns an empty Items instance with the same options.

const empty = users.clear()
every(check: (entity: E) => boolean): boolean

Tests whether all items pass the provided function.

const allAdults = users.every((u) => u.age >= 18)
some(check: (entity: E) => boolean): boolean

Tests whether at least one item passes the provided function.

const hasSenior = users.some((u) => u.age >= 65)
has(id: ItemId): boolean

Checks if an item with the given ID exists.

const exists = users.has(1)
get(id: ItemId): E | undefined

Gets an item by ID.

const user = users.get(1)
getIds(): ItemId[]

Returns an array of all item IDs.

const ids = users.getIds() // [1, 2, 3]
getEntities(): E[]

Returns an array of all items.

const allUsers = users.getEntities()
length: number

Gets the number of items in the collection.

console.log(users.length) // 3

Static Methods

Items.compare<E>(base: Items<E>, to: Items<E>): ItemsDiff

Compares two Items instances and returns the differences.

const before = new Items(users)
const after = before.update(1, { age: 31 })

const diff = Items.compare(before, after)
console.log(diff)
// {
//   added: [],
//   removed: [],
//   updated: [{ id: 1, changes: [...] }]
// }

Select Class

The Select class provides a fluent API for querying and transforming item collections.

Methods

take(len: number): Select<E>

Takes the first n items.

users.select((s) => s.take(2))
skip(len: number): Select<E>

Skips the first n items.

users.select((s) => s.skip(1))
filter(testFn: (entry: E, id: ItemId, index: number) => boolean): Select<E>

Filters items based on a predicate function.

users.select((s) => s.filter((user, id, index) => user.age > 25))
revert(): Select<E>

Reverses the order of items.

users.select((s) => s.revert())
sort(sortFn: (a: E, b: E) => number): Select<E>

Sorts items using a comparator function.

users.select((s) => s.sort((a, b) => a.age - b.age))
at(index: number): SingleSelect<E>

Returns a SingleSelect for the item at the given index.

const select = new Select(users.getIds(), users)
const single = select.at(0)
from(entities: Iterable<E>): Select<E>

Creates a new Select from the given entities.

const select = new Select(users.getIds(), users)
const newSelect = select.from([
  { id: 2, name: 'Bob', age: 25 }
])
on(entry: E): SingleSelect<E>

Creates a SingleSelect for the given entity.

const select = new Select(users.getIds(), users)
const single = select.on({ id: 1, name: 'Alice', age: 30 })

Chaining Selectors

Selectors can be chained for powerful queries:

const result = users.select((s) => 
  s.filter((u) => u.age >= 25)
    .sort((a, b) => a.age - b.age)
    .skip(1)
    .take(2)
)

Diff Tracking

Track changes between two Items instances:

import { Items, itemsDiff } from '@wszerad/items'

const before = new Items([
  { id: 1, name: 'Alice', age: 30 },
  { id: 2, name: 'Bob', age: 25 }
])

const after = before
  .update(1, { age: 31 })
  .add([{ id: 3, name: 'Charlie', age: 35 }])
  .remove(2)

const diff = itemsDiff(before, after)
console.log(diff)
// {
//   added: [3],
//   removed: [2],
//   updated: [{ id: 1, changes: [...] }]
// }

Advanced Usage

Custom ID Selector

Use a custom field as the ID:

interface Product {
  sku: string
  name: string
  price: number
}

const products = new Items<Product>(
  [
    { sku: 'ABC123', name: 'Widget', price: 9.99 },
    { sku: 'XYZ789', name: 'Gadget', price: 19.99 }
  ],
  { selectId: (product) => product.sku }
)

const widget = products.get('ABC123')

Automatic Sorting

Keep items sorted automatically:

const users = new Items(
  [
    { id: 3, name: 'Charlie', age: 35 },
    { id: 1, name: 'Alice', age: 30 },
    { id: 2, name: 'Bob', age: 25 }
  ],
  {
    sortComparer: (a, b) => a.age - b.age
  }
)

console.log(users.getIds()) // [2, 1, 3] - sorted by age

Complex Updates

Perform complex transformations:

// Increment age for all users over 25
const updated = users.update(
  (s) => s.filter((u) => u.age > 25),
  (user) => ({
    ...user!,
    age: user!.age + 1,
    senior: user!.age >= 65
  })
)

Immutability

All operations are immutable:

const original = new Items([
  { id: 1, name: 'Alice', age: 30 }
])

const updated = original.update(1, { age: 31 })

console.log(original.get(1)?.age) // 30 - unchanged
console.log(updated.get(1)?.age)  // 31 - new instance

Iteration

Items are iterable:

for (const user of users) {
  console.log(user.name)
}

const array = Array.from(users)
const names = [...users].map(u => u.name)

Single Item Selection

The select() method automatically returns a single item (or undefined) when using at() or on() methods:

const users = new Items<User>([
  { id: 1, name: 'Alice', age: 30 },
  { id: 2, name: 'Bob', age: 25 },
  { id: 3, name: 'Charlie', age: 35 }
])

// Select by index - returns single User or undefined
const firstUser = users.select((s) => s.at(0))
console.log(firstUser?.name) // 'Alice'

const secondUser = users.select((s) => s.at(1))
console.log(secondUser?.name) // 'Bob'

// Select by ID - returns single User or undefined
const user = users.select(2)
console.log(user?.name) // 'Bob'

// Out of bounds returns undefined
const notFound = users.select((s) => s.at(10))
console.log(notFound) // undefined

// Chain operations before selecting single item
const oldestUser = users.select((s) => 
  s.sort((a, b) => b.age - a.age).at(0)
)
console.log(oldestUser?.name) // 'Charlie' (age 35)

// Get just the ID instead of the full item
const oldestId = users.selectId((s) => 
  s.sort((a, b) => b.age - a.age).at(0)
)
console.log(oldestId) // 3

TypeScript Support

Full TypeScript support with generics:

interface User {
  id: number
  name: string
  age: number
}

const users = new Items<User>() // Fully typed

// Type inference works automatically
const names: string[] = users
  .select((s) => s.filter((u) => u.age >= 30))
  .map(u => u.name)

Types

type ItemId = string | number

type Selector<E> = 
  | ((selector: BaseSelect<E>) => BaseSelect<E>) 
  | ItemId 
  | Iterable<ItemId>

type Updater<E> = 
  | ((entity: E | undefined) => E)
  | Partial<E>

type ItemsOptions<E> = {
  selectId?: (entity: E) => ItemId
  sortComparer?: false | ((a: E, b: E) => number)
}

interface ItemsDiff {
  added: ItemId[]
  removed: ItemId[]
  updated: ItemDiff[]
}

interface ItemDiff {
  id: ItemId
  changes: any[]
}

License

MIT © Wszerad Martynowski

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Repository

https://github.com/wszerad/items