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

@codetonight/antepaste-core

v0.1.0

Published

Pure TypeScript clipboard-history model for Antepaste. No Electron, no native code.

Readme

@codetonight/antepaste-core

The pure TypeScript model of Antepaste's clipboard history: the clips, the towers they stack into, the schema that stores them, and the queries that read and write it. No Electron and no native code — ESLint rejects any electron or electron/* import here, and any filesystem import in src, so it stays that way.

Core never opens a database. It is handed one and works through it, which is what lets the same code back the desktop app, the MCP server and an in-memory test database without knowing which it is talking to.

What is in it

| | | | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | hashContent | The SHA-256 that decides whether two copies are the same copy, and therefore whether a repeat copy moves an existing clip to the top or stacks a new one. | | generatePreview | The one-line summary shown in the history list. | | detectLanguage, formatTowerAsContext | Renders a tower as one markdown block for pasting into a chat, fencing clips that are clearly code. | | validateTowerName, validateUuid, validateContent, validateTheme, validateSettingsKey | The checks every value crossing into the model passes. Each returns null or a ValidationError. | | isOpenableUrl | Whether a string is safe to hand to the operating system as a link. http and https only. | | decideCapture, DEFAULT_EXCLUDED_BUNDLES | Whether a copy is recorded at all, and under what flags. See "The capture policy" below. | | createWalkSession | The cursor and undo behind the paste-then-Up gesture. See "The walk" below. | | MIGRATIONS, applyMigrations, latestMigrationVersion | The schema as an append-only list of numbered steps, currently 1 to 9. | | createRepositories and the nine individual factories | Every query, grouped by the table it reads. | | SqliteDatabase, DbProvider | The database contract, described structurally so core is typed without the driver. |

The provider pattern

Repositories take a function that returns the current database, not a database:

import Database from 'better-sqlite3';
import { applyMigrations, createRepositories } from '@codetonight/antepaste-core';

const db = new Database('antepaste.db');
db.pragma('journal_mode = WAL');
applyMigrations(db, (version) => console.log(`applying migration ${version}`));

const { clipRepo, towerRepo } = createRepositories(() => db);

That indirection is the whole design. A factory capturing a handle would go on using it after it was closed or replaced, which is why tests — each building a fresh in-memory database — can construct their repositories once and have every case run against a different one. It is also what lets an application reopen or move its file without rebuilding anything. packages/core/test/repositories-sync.test.ts asserts the property directly, so replacing the provider with a cached handle fails as itself rather than as a dozen unrelated errors.

The SqliteDatabase interface is structural rather than an import of better-sqlite3's own types, so a consumer on better-sqlite3 11 and one on 13 are both satisfied without agreeing on a version of @types/better-sqlite3. better-sqlite3 ships no typings itself — there is not one .d.ts in the 13.0.3 tarball — so @types/better-sqlite3 is what every consumer's handle is typed through, and what the contract has to fit.

It fits it without an adapter, measured rather than assumed: a Database assigns to SqliteDatabase under @types/better-sqlite3 7.6.13 and 9.6.0, on TypeScript 5.9 and 6.0. That needed the bind positions widened to SqliteBindParam. Those typings make prepare generic over BindParameters extends unknown[] | {} and return Statement<[Bind]> for a named-parameter object, which types the bind position as an object — and an object type excludes null, so on TypeScript 5.x, where the call resolves to the union of both branches, a real handle did not satisfy the interface at all and a consumer had to write an adapter to get past it. SqliteBindParam is a bind value or a named-parameter object whose values are bind values — precise enough to admit that branch, narrow enough that a boolean, an undefined and an object of objects all stay compile errors. undefined is the one that earns its keep: the driver does not object to it, it stores a null and reports success. test/sqlite-contract.test.ts checks all of it at compile time and at run time.

The capture policy

decideCapture answers one question — should this copy be recorded, and under what flags — from an input that contains everything it needs, so it reads no clipboard, no clock and no database. That is what lets the privacy rulings in docs/decisions/0001-step0-councils.md §4 be checked by the test suite rather than asserted in a README.

The rules are applied in order, and a refusal always outranks a capture:

  1. A type asking not to be recorded. The four nspasteboard.org markers, the three per-application ones that predate them, and Windows' ExcludeClipboardContentFromMonitorProcessing all refuse on presence alone. CanIncludeInClipboardHistory is a DWORD rather than a marker, so a platform adapter must render it as CanIncludeInClipboardHistory=0 for it to mean anything; a bare name or =1 decides nothing.
  2. An application we never record. A declared org.nspasteboard.source is believed. Otherwise the application in front is used — but only while it is still evidence, which the next rule qualifies. DEFAULT_EXCLUDED_BUNDLES holds the six macOS bundle identifiers and four Windows image names, compared without regard to case.
  3. A copy nobody can be credited with. No declared source and an application switch inside the capture window means the application in front now is a coincidence, not a source, so the copy is refused as unknown-source rather than credited to whoever happens to be there.
  4. A secret. A run of four to eight digits within forty characters of code, OTP, verification, passcode or 2FA; or twenty or more characters with no whitespace carrying more than 3.5 bits of Shannon entropy each. Both are concealed and transient. They differ in what happens next, and the difference is the strength of the evidence:
    • A one-time code expires two minutes later, which is what makes pasting it a second time work without leaving it in history for a month. Its preview is the message with the digits taken out — Your code is 1234 becomes Your code is …. Masking such a message by position would leak the code, because a code sits at the end of the sentence announcing it: the first draft of this produced Yo…34, which is half of it.
    • An entropy-only match is not destroyed. Twenty unbroken high-entropy characters is a guess, and in information terms it is indistinguishable from a git commit hash (3.97 bits per character) or a package integrity digest (4.86). Destroying those two minutes later, with a pin no defence, would quietly delete the most commonly copied thing in a developer's day. So it is hidden and never synced, and it stays: a guess may cost visibility, never the data. Its preview shows two characters at each end, which is enough to tell two tokens apart.
  5. The reserved pck-cold- prefix, which is a marker rather than something a user copied.

Images and files pass through rules 1 to 3 exactly as text does, which is the other half of that ruling. They do not reach rules 4 and 5, which read text an image does not have and would mask half the deep paths anyone copies.

The entropy rule's known cost is that a long unbroken URL, path, hash or identifier can read as a token and be hidden behind a mask. shannonEntropyPerCharacter is exported so the threshold is a number anyone can compute rather than a claim. It would be wrong if, in use, masked ordinary strings outnumbered caught secrets — the fix then is to require a character-class mix as well as entropy, not to drop the rule. It would also be wrong if a real credential were commonly pasted with no accompanying word and needed an expiry to be safe; the fix there is a narrower classifier, not a destructive default. Both asymmetries were asked for by a sovereign council (seal a78eb8eb602096c5).

The walk

createWalkSession is the paste-then-Up gesture with no operating system in it: where the cursor is, what should be written, and whether a write is a preview or the real thing. It writes nothing. It says what should be written, and the caller makes whichever of those writes it is in a position to make — on macOS the native addon has already replaced the text in the field by the time a step is reported, so only the normal write at the end is the engine's to do.

Five invariants, each with its own cases in test/walk-session.test.ts:

  • The cursor clamps at both ends and says so. A step past either end leaves the cursor where it was, writes nothing, and comes back with clamped: true, so a caller can tell "you are already at the oldest" from "you moved".
  • Duplicates are never collapsed while a gesture is running. Two identical items are two items and walking past them takes two taps. Collapsing them would make the same Up tap move one place sometimes and two at others, based on content the user cannot see. Deduplicating is the history's job, at the moment a copy is recorded.
  • restore returns to index 0 and makes no normal write. It previews the original and leaves the gesture open, so Escape puts things back without ending the walk.
  • commit at index 0 writes nothing; at any other index it writes exactly one normal write. Index 0 is where the paste already left things, and writing it again would be a clipboard change nobody asked for.
  • A history arriving mid-gesture waits. Swapping the list under a walk would move the cursor to a different item without the user touching anything — and a copy landing mid-gesture is exactly what the paste being walked away from tends to produce. Between gestures there is nothing to disturb, so it is taken up at once.

A closed session accepts a step, a restore or a commit and ignores it rather than throwing. A native event stream can deliver a step after a disarm, and dropping a late event is the right answer to that; crashing the application is not.

better-sqlite3

A peer dependency, range >=11.7.0 <14: the consumer owns the driver and the file, core owns the schema and the queries. The range covers TWR's locked 11.10.0, so the existing desktop app can adopt core without moving its driver. Tests run against 13.0.3, which ships N-API prebuilds inside its published tarball for every platform in CI, so there is no install script and no compile step.

Schema

Migrations 1 to 6 are TWR's, moved across byte for byte. Three are new:

  • 7 — a blobs table keyed by the same content hash the rest of the model uses, and a nullable blob_hash on clip_items that references it, so a large clip can live on disk once instead of inline in every row.
  • 8 — where a clip came from and whether it may leave the device: device_id, hlc, source_bundle, concealed, transient, expires_at, sync and deleted_at. The last is a tombstone rather than a DELETE, because a row simply removed would be sent back by the next device that still holds it. No pinned column is added; clip_items has had one since migration 1.
  • 9 — op_log and an index on hlc, a replayable record of changes. Stamps compare as strings, so two devices agree on an order without trusting each other's clocks.

markExpired, tombstone and setFlags write these columns. The read methods do not yet filter tombstoned clips out — that belongs with the sync work that will consume them.

Provenance

Extracted from TWR (CodeTonight SA) by V>> (Lourens Cornelius "Laurie" Scheepers), with assistance of Claude Opus 5 (claude-opus-5) and Claude Fable 5.1 (claude-fable-5-1).

Licence: MIT (see LICENSE).