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

@burakboduroglu/skadi

v0.1.5

Published

<div align="center">

Readme

Skadi

Subscription tracking that rides a PocketBase instance you already run.

npm License Self-hosted No telemetry No Docker Bun


Skadi is not an application you deploy. It is three migrations, two hook files and one static page, copied into a PocketBase directory you already have. The database, auth, HTTP server, admin UI and backups are already running — a dedicated subscription tracker would duplicate all of them.

What it is

Add a subscription by pasting a link. Google Play, a Wikipedia article, or a direct image URL: the server resolves it to a name and a logo, so a full entry is usually a link, an amount and a date.

Charge dates advance on their own, clamped to the day a subscription actually bills on, so one charged on the 31st does not sink to the 28th after a February. Amounts can be TRY, USD, EUR or GBP, with totals quoted in whichever you pick and rates cached six hours. A stale rate is labelled as stale, never passed off as today's, and anything unconvertible is counted rather than silently zeroed.

Cashback is one number per subscription, in its own currency. No campaign windows, no expiry alerting — when a deal changes you edit the number.

Highlights

| | Feature | How it works | | --- | ------- | ------------ | | 🧩 | No infrastructure of its own | Files copied into an existing PocketBase. No container, no second database, no extra port. | | 🔗 | Paste a link, get a name and a logo | Play og:image/og:title, Wikimedia File: via the MediaWiki API, or any direct image by content type. | | 📅 | Dates that move themselves | Passed charges roll forward by cycle, clamped to an anchor day. | | 💱 | Four currencies, one honest total | Stale rates labelled, unconvertible rows counted, never zeroed. | | 🔒 | Superuser-only by default | Null API rules: anonymous and ordinary tokens get 403 before any proxy. | | 🌍 | Turkish and English | Two dictionaries and a lookup. No i18n runtime. | | 📵 | No telemetry | Two outbound requests exist: the FX quote, and the link you asked it to resolve. | | 📊 | Dashboard optional | A Glance widget is included; plain JSON for anything else; nothing at all is fine. |

Footprint

Measured on a 2 vCPU / 3.7 GiB Debian box running PocketBase 0.40:

| | | | --- | --- | | Installed files | 96 KB — migrations 12 KB, hooks 24 KB, page 60 KB | | Database growth | ~45 KB for the three collections plus a handful of subscriptions | | Resident memory | ~22 MB, and that is the whole PocketBase process | | Extra processes | none | | Extra ports | none |

The memory figure is PocketBase serving Skadi and nothing else, so it is an upper bound rather than Skadi's own cost — the hooks run inside PocketBase's own JS runtime and add no process of their own. If PocketBase is already running, the marginal cost of Skadi is closer to the file sizes than to the memory line.

Install

bunx @burakboduroglu/skadi install /path/to/pocketbase

Writes pb_migrations/, pb_hooks/ and pb_public/subs/. It never touches pb_data/, and refuses a directory holding neither a pocketbase binary nor a pb_data.

Then restart PocketBase so the migrations run. If it was already running, stop it before copying and start it afterwards — PocketBase watches pb_hooks and restarts itself when those change, which races a service manager doing the same.

The page is served at /subs/. Two config snippets are yours to place:

skadi caddy                                    # required
skadi glance --url https://your.host/subs/     # optional, --lang en|tr

How it works

browser ──▶ /subs/                            static page, superuser login
        └─▶ /api/collections/subscriptions    superuser-only CRUD
        └─▶ /api/subs/rates                   superuser-only, FX for the totals

dashboard ─▶ 127.0.0.1:8090/api/subs/summary  no auth, unreachable from outside

subscriptions holds the contracts, cards the payment methods you pick from, and fx_rates a single cached row updated in place.

Security

Two layers, either sufficient alone:

  1. PocketBase rules — both collections are superuser-only, enforced with no proxy involved.
  2. Whatever you put in front — /subs/ is a login form anyone can reach until you gate it. Skadi does not ship an auth layer.

GET /api/subs/summary takes no token, because a dashboard reads it over loopback rather than through your proxy. The line skadi caddy prints is what removes the only path in from outside. Drop it and the endpoint is public.

The page authenticates as a superuser and keeps that token in localStorage — a deliberate trade for a single-operator tool, and why every rendered field is escaped. Details in SECURITY.md.

What it deliberately does not do

No reminders. No campaign expiry tracking. No scheduled job — rates refresh when something asks. No multi-user. No import.

Stack

PocketBase for storage, auth, HTTP and backups. JavaScript in PocketBase's hook runtime, and a SolidJS + Vite UI compiled to static files under pb_public/subs/ — no CSS library, no UI server. Bun as the package manager.

The name is Skaði, the Norse goddess of winter and the mountains, who chose a husband by looking only at the feet. Apt for something that makes you look at what you are actually paying.

Develop the UI

Source lives in web/ (SolidJS + Vite, visual language shared with Nors). The shipped page is built output — pb_public/subs/ is not committed; prepack builds it into the npm tarball, and CI builds it and runs the tests on every push.

bun --cwd web install
bun run build      # root script — emits pb_public/subs/
bun run dev        # local UI; /api proxied to :8090 (SKADI_PB_PORT to move it)
bun run test       # unit tests (bun test)

Against a tunneled production box: ssh -N -L 8090:127.0.0.1:8090 hetzner, then bun run dev — the browser stays on localhost, so neither CORS nor Cloudflare Access is in the path.

Contributing

Bug reports, logo-resolution failures and translation fixes are welcome; the things above are not. See CONTRIBUTING.md and CODE_OF_CONDUCT.md. Security problems go through a private advisory, never a public issue.

License

MIT — see LICENSE.