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

@singapore-editor/spellcheck

v0.1.1

Published

English spellcheck engine for text the editor paints itself. A worker holds the dictionary; the page creates one `SpellcheckService` and shares it between editors.

Readme

@singapore-editor/spellcheck

English spellcheck engine for text the editor paints itself. A worker holds the dictionary; the page creates one SpellcheckService and shares it between editors.

US and British spellings are both accepted, plus software vocabulary. The dictionary is built from SCOWL and cspell's word lists by bun run build:dictionaries; see THIRD_PARTY_NOTICES.

Usage

In an editor: one service per page, one plugin per editor.

import { Editor } from '@singapore-editor/core/editor'
import {
  createSpellcheckPlugin,
  EDITOR_SPELLCHECK_FEATURE,
  SpellcheckService,
} from '@singapore-editor/spellcheck'

const service = new SpellcheckService()
const editor = new Editor(element, { plugins: [createSpellcheckPlugin({ service })] })
const spelling = editor.getFeature(EDITOR_SPELLCHECK_FEATURE)
const issue = spelling?.issueAt(offset) // { start, end, word } or null
const suggestions = await spelling?.suggestions(offset)
spelling?.replace(offset, suggestions[0]) // one undoable edit

Plain text and Markdown prose are checked; scope: 'proseAndCode' adds comments and strings in code. Markdown code, link targets and labels, and anything an inline replacement stands in for are skipped. The word being typed is not marked until the caret leaves it.

On its own:

import { SpellcheckService, tokenizeSpellWords } from '@singapore-editor/spellcheck'

const spellcheck = new SpellcheckService()
const text = 'the list settles befor the cursor'
const words = tokenizeSpellWords(text)
const misspelled = new Set(await spellcheck.check(words.map((word) => word.word)))
const marks = words.filter((word) => misspelled.has(word.word))
const suggestions = await spellcheck.suggest('befor') // ['before', …]
spellcheck.setAcceptedWords(['fregat'])
spellcheck.dispose()

Exports

  • createSpellcheckPlugin({ service, scope }) and EDITOR_SPELLCHECK_FEATURE: issueAt(offset), suggestions(offset, limit), replace(offset, word) and setAcceptedWords(words).
  • SpellcheckService: check(words), suggest(word, limit), setAcceptedWords(words) and dispose(). The worker starts on the first request.
  • tokenizeSpellWords(text, { mode, excluded }): the words to check, with offsets. It skips acronyms, camelCase, words with digits or _, runs containing letters outside ASCII, URLs, email addresses, paths and the excluded ranges. mode: 'code' splits camelCase and snake_case instead.

Language and work limits

The bundled dictionary checks English. There is no natural-language detection. Prose candidates contain ASCII letters and internal straight or typographic apostrophes. hola mundo is submitted to the English checker; שלום, привет and café are skipped. This also skips accented words that occur in English. Programming-language selection controls which editor regions are prose.

Whitespace-delimited chunks longer than 256 UTF-16 code units are skipped before structured-text classification. The editor skips prose lines and code regions longer than 16,384 code units before reading them. This bounds synchronous work on generated text and large pastes. Short surrounding chunks remain checkable through tokenizeSpellWords.

Worker setup and posting failures reject check and suggest, settle every outstanding request, and terminate that worker. A later service request creates a fresh worker and resends accepted words. An editor that sees a check failure stops requesting checks for its lifetime so typing cannot start a retry loop. Failed accepted-word synchronization retains the local list for the next worker and notifies listeners. Disposal rejects outstanding requests and prevents restart.

Language-support follow-up

Add an explicit languages selection separate from syntax language and default it to English. Start with manually selected dictionaries; mixed-language documents accept a word found in any selected dictionary, and merge/deduplicate suggestions in configured language order. Automatic language detection is outside this scope.

Load dictionaries lazily in the shared service from a language manifest. Each entry must record its source, version, attribution and verified permissive data license. Review these for each selected dictionary; the engine license does not cover dictionary data. Bundle English only by default and load other selected assets on demand.

Change tokenization with dictionary selection: Unicode letters/marks, language-specific word segmentation, apostrophe policy and NFC normalization must preserve original UTF-16 offsets. Keep script selection explicit so mixed scripts are checked only when a selected dictionary supports them. Define case folding per dictionary rather than globally stripping accents.

Treat a language change as a new dictionary generation. Clear controller verdicts, pending-word sets, cached line tokenization and painted issues; ignore replies from the previous generation. Only publish the new generation once all selected dictionaries load. A load failure leaves the previous complete selection active and reports the failed selection to the host. Accepted words remain user-owned and are synchronized into the new generation.

Benchmark interpretation

bun run bench:engine evaluates the common Norvig spell-testset1/2 pairs. It reports total pairs, target-word coverage, and misspellings accepted despite a covered target separately. Suggestion ranking runs only on pairs where the dictionary accepts the target and rejects the typo. eligibleRankingPairs is the denominator of eligibleTop1Percent and eligibleTop5Percent. Compare engines using the same pair set and report coverage and missed typos alongside ranking. These conditional ranking percentages do not measure overall typo-detection accuracy.