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

@fengzhubao/bookmark-lens

v0.2.0

Published

Bookmark tree analysis core for BookmarkLens — flatten, sort, detect duplicates, find empty folders, group by domain.

Readme

@fengzhubao/bookmark-lens

Bookmark tree analysis core — flatten, sort, detect duplicates, find empty folders, group by domain.

A structured analysis library for browser bookmark trees (Chrome / Edge chrome.bookmarks tree). No DOM, no extension API, no UI framework dependencies — runs in Node, browser extensions, CLIs, or anywhere else JavaScript runs.

Powers the BookmarkLens browser extension as its pure-logic layer.

Install

npm install @fengzhubao/bookmark-lens

Requires Node >= 20 or any modern browser (depends on the standard WHATWG URL).

Quick start

import {
  flattenBookmarkTree,
  findDuplicateBookmarks,
  findEmptyFolders,
  groupBookmarksByDomain
} from "@fengzhubao/bookmark-lens";

// Inside a Chrome/Edge extension
chrome.bookmarks.getTree((tree) => {
  const rows = flattenBookmarkTree(tree);

  // Find duplicate URLs
  const { groups, duplicateIds } = findDuplicateBookmarks(rows);
  console.log(`${groups.length} duplicate URL groups, ${duplicateIds.size} affected bookmarks`);

  // Find empty folders
  const empty = findEmptyFolders(rows);
  console.log(`${empty.folders.length} empty folders`);

  // Group by domain
  const byDomain = groupBookmarksByDomain(rows, { minGroupSize: 2 });
  console.log(`${byDomain.totalDomains} domains, ${byDomain.totalBookmarks} bookmarks`);
});

All functions are pure — they never mutate their inputs.

API

Data model

| Function | Purpose | |----------|---------| | flattenBookmarkTree(nodes) | Flatten a nested bookmark tree into an array of rows. Each row contains id / title / url / domain / path / folderPath / dateAdded / depth / isFolder / childCount and more. | | summarizeRows(rows) | Compute { bookmarkCount, folderCount, datedCount, label }. | | parseBookmarkUrl(url) | Safely parse a URL, returning { href, protocol, hostname, origin, isValid, error }. | | formatBookmarkTime(value) | Format a dateAdded millisecond timestamp into a human-readable string. |

Sorting and view state

| Function | Purpose | |----------|---------| | sortBookmarkRows(rows, sortSpec) | Stable sort by a given key, falling back to original tree order on ties. | | getNextSortSpec(currentSort, key) | Three-state sort state machine: default → asc → desc → default. | | createDefaultBookmarkViewState() | Create an empty view state (query / sort / domainScope / folderScopeId). | | resetBookmarkViewState() | Reset the view state. | | rowMatchesDomainScope(row, scope) | Domain-scope predicate. | | rowMatchesFolderScope(row, id) | Folder-subtree predicate. |

Analysis

| Function | Purpose | |----------|---------| | normalizeBookmarkUrl(url, options?) | Normalize a URL: lowercase hostname, drop default ports, strip trailing slashes, remove tracking params (utm_*, fbclid, gclid, etc.). | | findDuplicateBookmarks(rows, options?) | Group bookmarks by normalized URL, returning { groups, duplicateIds }. Options are forwarded to normalizeBookmarkUrl. | | findEmptyFolders(rows) | Find leaf folders with no children (excluding system roots). Returns { folders, folderIds }. | | groupBookmarksByDomain(rows, options?) | Group bookmarks by domain. minGroupSize filters out singletons. Returns { groups, totalDomains, totalBookmarks }. |

Operation plans (read-only)

Generate explainable, undo-able plan objects for write operations. The plans themselves never mutate any bookmarks — the caller is responsible for executing them.

| Function | Purpose | |----------|---------| | createRenamePlan(row, title) | Rename plan. | | createEditUrlPlan(row, url) | Change URL plan. | | createCreateBookmarkPlan(parentRow, data) | Create-bookmark plan. | | createCreateFolderPlan(parentRow, data) | Create-folder plan. | | createMovePlan(row, options) | Move plan. | | createDeletePlan(row, options) | Delete plan (folders cascade automatically). | | isProtectedBookmarkRow(row) | Is the row a protected root (id 0 / 1 / 2 or depth 0)? | | canUndoHistoryEntry(entry) | Is the history entry undoable? | | trimOperationHistory(history, limit?) | Truncate history (default 100 entries). | | createHistoryEntry(plan, result?) | Build a history entry from an executed plan. |

Export

| Function | Purpose | |----------|---------| | exportRows(rows, format) | Export rows as "json" or "csv" string. | | getBookmarkContextMenuItems(row, options?) | Context-menu item data (UI side renders). |

URL normalization defaults

normalizeBookmarkUrl applies these rules by default (each configurable via options):

  • Lowercase scheme and hostname
  • Drop default ports (http:80 / https:443 / ws:80 / wss:443 / ftp:21)
  • Strip trailing slashes
  • Remove tracking params: utm_*, fbclid, gclid, mc_cid, mc_eid, ref, ref_src, _ga, igshid
  • Sort remaining query params by key
  • Preserve hash fragment (many SPAs use hashes for routing)

Configurable options:

normalizeBookmarkUrl(url, {
  stripHash: false,              // default keeps hash
  stripTrackingParams: true,     // default strips tracking
  stripTrailingSlash: true,      // default removes trailing slash
  additionalTrackingParams: []   // extra param names to strip
})

License

GPL-3.0-or-later. Copyright © 2026 luppiter.

See LICENSE for the full text.