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

@speles7172/backup-bridge

v0.4.1

Published

Lambda handlers for @speles7172/backup-client — the scheduled runner and the Athena bridge.

Readme

@speles7172/backup-bridge

Setting this up end to end — tables, infrastructure, schedule, querying, restore — is docs/BACKUP.md.

The two Lambda handlers behind @speles7172/backup-client, deployed by infra/modules/backup.

EventBridge ──hourly──▶ runner ──▶ Postgres ──▶ S3 (Parquet)
                          │
                          └──▶ athena bridge ──▶ Glue / Athena

| Entry | Handler | What it is | |---|---|---| | ./runner | dist/runner-handler.handler | Decides whether a backup is due, takes it, registers the tables, prunes, reaps | | ./athena | dist/athena-handler.handler | Issues the catalog DDL from outside the VPC |

Why there are two

The runner has to be in the VPC to reach the database. A Lambda in private subnets cannot reach Athena without a NAT gateway or an interface endpoint — so the DDL goes through a second function that is not in the VPC. If your database is publicly reachable, leave ATHENA_BRIDGE_FUNCTION_NAME unset and the runner talks to Athena directly.

Configuration

Deployment shape is environment; the schedule is not. It lives in backup_settings so an operator can change it from @speles7172/backup-console — a schedule that needs a deploy to change is a schedule nobody can change at 2am.

| Variable | | |---|---| | BACKUP_BUCKET | Required. Where backups are written | | BACKUP_ROOT | Key prefix. Empty is a real answer — one application per bucket | | DB_SECRET_ARN | Secret holding the database credentials | | DB_HOST, DB_PORT, DB_NAME | Where the database is, when the secret does not say. An RDS-managed master password produces a secret of only {"username","password"}, so these are required with one | | BACKUP_TABLES | Comma-separated. Empty discovers every base table in public | | APP_FILES_BUCKET | The files half. Absent means database runs only | | GLUE_DATABASE | Absent skips catalog registration entirely | | ATHENA_BRIDGE_FUNCTION_NAME | Absent talks to Athena directly | | ATHENA_WORKGROUP, ATHENA_OUTPUT_LOCATION | Unless the workgroup sets its own |

Invoking it

{ "scheduled": true }        // the hourly tick — usually decides to do nothing
{ "store": "db" }            // a manual run from the console
{ "store": "s3" }

Things that will come up

The tick is gated on the last successful run, per store. A run that failed or is still going occupies the slot without producing a backup, so gating on the newest run of any status is how a failing backup silently stops retrying. Each store is asked about separately for the same reason: db succeeding says nothing about whether the files were copied, and one answer for both leaves the store that failed unretried until some later slot.

A run that could not export every table is an error, not a success. backup() resolves rather than throwing when individual tables fail — it writes the manifest and marks it error — so the manifest, not the absence of an exception, decides. A partial run must not claim the slot; the gap is otherwise invisible until a restore needs the missing table.

A snapshot whose Athena registration failed is still a snapshot. The data is in S3 and a restore from it works, so the run is recorded success and carries a catalog-pending note instead. Later ticks re-apply the catalog from that run's manifest — two DDL statements — and clear the note. Marking the run error would hide a usable backup from the console and make the next tick take a whole fresh dump to fix a CREATE TABLE.

Reconciliation runs after the backups, and is skipped only when the tick itself issued the DDL — not merely when a db run was scheduled. Those differ exactly when the scheduled run fails: it registers nothing, so gating on the schedule would strand the older pending snapshot for as long as the backup keeps failing, which is when an operator most needs to query it.

The run row is written before the backup starts. Recording only on success makes a killed runner indistinguishable from a run that never happened — and a killed runner is the common failure, because it never reaches its own error handler.

The reaper runs before the gate, on every tick, including when backups are switched off. A manual run whose Lambda died still needs marking, and it should not wait for somebody to re-enable the schedule. Its threshold is 20 minutes against a 15-minute function ceiling, so a run finishing near the limit is never reaped out from under itself.

Retention prunes after the backup, never before. Pruning first deletes the oldest run to make room for one that then fails, leaving fewer backups than there were when the tick started. It is also clamped to what the bucket's lifecycle will honour — a longer setting is a promise S3 will not keep.

One store failing does not stop the other. They are separate runs for separate reasons; a slow file copy should not cost a database backup that already succeeded.

The Athena bridge reports failures in band as { error } rather than as a Lambda FunctionError. A statement Athena rejected and a bridge that is down are different problems, and a caller that cannot tell them apart will retry the first one for ever.

Nothing is created at module load. A Lambda that throws while initialising fails every future invocation on the same container with an error naming the module rather than the cause — and this function spends most of its life deciding not to run.

What it does not do

  • Restore. createRestorer is in backup-client; wiring it to an endpoint is the application's decision, because it overwrites a production database.
  • Provision anything. See infra/modules/backup.
  • Create its own tables. BACKUP_TABLES_SQL in backup-client is the DDL; where it lives and who may read it is the application's call.