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

@marianmeres/item-collection

v1.3.5

Published

[![NPM Version](https://img.shields.io/npm/v/@marianmeres/item-collection)](https://www.npmjs.com/package/@marianmeres/item-collection) [![JSR Version](https://jsr.io/badges/@marianmeres/item-collection)](https://jsr.io/@marianmeres/item-collection) [![Li

Downloads

514

Readme

@marianmeres/item-collection

NPM Version JSR Version License: MIT

A versatile, high-performance TypeScript utility class for managing collections of items with advanced features.

Features

  • Ordered collection with customizable sort function
  • Uniqueness and cardinality constraints to control collection behavior
  • Active item tracking with navigation (next/prev/first/last)
  • Flexible tagging system with per-tag cardinality limits
  • O(1) lookups via automatic property indexing
  • Full-text search integration via @marianmeres/searchable
  • Reactive subscriptions for change notifications
  • Serialization with dump/restore support

Installation

# Deno
deno add jsr:@marianmeres/item-collection
# Node.js
npm install @marianmeres/item-collection

Quick Start

import { ItemCollection } from '@marianmeres/item-collection';

// Create a collection
const collection = new ItemCollection<{ id: string; name: string }>([
  { id: '1', name: 'Alice' },
  { id: '2', name: 'Bob' },
  { id: '3', name: 'Charlie' },
]);

// Navigate items
collection.setActiveFirst();       // { id: '1', name: 'Alice' }
collection.setActiveNext();        // { id: '2', name: 'Bob' }

// Find items (O(1) lookup)
collection.findById('3');          // { id: '3', name: 'Charlie' }
collection.findBy('name', 'Bob');  // { id: '2', name: 'Bob' }

// Tag items for flexible categorization
collection.applyTag(collection.at(0), 'featured');
collection.applyTag(collection.at(2), 'featured');
collection.getByTag('featured');   // [Alice, Charlie]

// Subscribe to changes
const unsubscribe = collection.subscribe(({ items, active, size }) => {
  console.log(`Collection has ${size} items, active: ${active?.name}`);
});

Configuration Options

const collection = new ItemCollection<T>(initialItems, {
  // Maximum number of items (default: Infinity)
  cardinality: 100,

  // Prevent duplicate items by id (default: true)
  unique: true,

  // Property name used as unique identifier (default: "id")
  idPropName: 'id',

  // Allow next/prev navigation to wrap around (default: false)
  allowNextPrevCycle: true,

  // Allow tags without explicit configuration (default: true)
  allowUnconfiguredTags: true,

  // Pre-configure tags with cardinality limits
  tags: {
    selected: { cardinality: 5 },  // max 5 items can have this tag
    favorite: { cardinality: 1 },  // only 1 item can be favorite
  },

  // Auto-sort function applied on add
  sortFn: (a, b) => a.name.localeCompare(b.name),

  // Transform items before adding
  normalizeFn: (item) => ({ ...item, createdAt: new Date() }),

  // Enable full-text search
  searchable: {
    getContent: (item) => `${item.name} ${item.description}`,
  },
});

Common Use Cases

Selection Management

const items = new ItemCollection<Product>(products, {
  tags: { selected: { cardinality: Infinity } }
});

// Toggle selection
items.toggleTag(product, 'selected');

// Get selected items
const selected = items.getByTag('selected');

// Clear selection
items.deleteTag('selected');

Single Active Item (Radio-like)

const tabs = new ItemCollection<Tab>(tabList);

// Set active by item reference
tabs.setActive(tabs.at(0));
console.log(tabs.active);  // first tab

// Set active by index
tabs.setActiveIndex(2);
console.log(tabs.active);  // third tab

// Navigate
tabs.setActiveNext();      // fourth tab (or stays if at end)
tabs.setActivePrevious();  // back to third tab

// Clear active state
tabs.unsetActive();
console.log(tabs.active);  // undefined

Tip: For multiple "active-like" states (e.g., selected + focused), use tags with cardinality: 1 constraint instead.

Keyboard Navigation

const menu = new ItemCollection<MenuItem>(menuItems, {
  allowNextPrevCycle: true  // wrap around at ends
});

document.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowDown') menu.setActiveNext();
  if (e.key === 'ArrowUp') menu.setActivePrevious();
  if (e.key === 'Home') menu.setActiveFirst();
  if (e.key === 'End') menu.setActiveLast();
});

Searchable Collection

const contacts = new ItemCollection<Contact>([], {
  searchable: {
    getContent: (c) => `${c.name} ${c.email} ${c.phone}`,
  },
});

contacts.addMany(fetchedContacts);

// Search with different strategies
contacts.search('john');                    // prefix match (default)
contacts.search('[email protected]', 'exact');  // exact match
contacts.search('jon', 'fuzzy');            // fuzzy match

Persistence

// Save state
localStorage.setItem('myCollection', collection.dump());

// Restore state
collection.restore(localStorage.getItem('myCollection'));

// Or create new instance from JSON
const restored = ItemCollection.fromJSON<MyItem>(savedJson);

API Reference

For the complete API documentation, see API.md.

Quick Reference

| Category | Methods | |----------|---------| | Collection | add, addMany, remove, removeAt, removeAllBy, move, clear, patch, patchMany, toggleAdd | | Navigation | setActive, setActiveIndex, unsetActive, setActiveNext, setActivePrevious, setActiveFirst, setActiveLast | | Lookups | at, exists, findById, findBy, findAllBy, findIndexBy, findAllIndexesBy, search | | Tagging | applyTag, removeTag, hasTag, toggleTag, getByTag, getIndexesByTag, configureTag, deleteTag | | Serialization | toJSON, dump, restore, fromJSON | | Reactivity | subscribe | | Properties | size, items, active, activeIndex, isFull, config, idPropName, searchable |