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

@gravlens/crons

v0.2.2

Published

Lens port of @convex-dev/crons (verbatim source; convex/convex-test aliased to lens/lens-test)

Readme

Lens

A drop-in replacement for the Convex API, powered by SpacetimeDB 2.0.

Lens publishes the exact convex package surface: same import paths, same exported names, same type signatures. A Convex app compiles and runs against Lens with no source changes. Underneath, your functions execute in a SpacetimeDB TypeScript module instead of the Convex backend.

How it works

Lens is three layers:

  1. lens: the convex-compatible npm package, covering all eight entry points (convex/values, convex/server, convex/react, convex/browser, convex/nextjs, convex/react-auth0, convex/react-clerk, and the root).
  2. lens-runtime: the server core, bundled together with your convex/ folder into a SpacetimeDB module. It provides the Convex execution model: function registry and dispatch, document store, scheduler, file storage, auth, and components.
  3. lens-cli: replicates the Convex CLI (dev, deploy, codegen, run, env, logs, data, import, export, mcp, and more) by driving the Spacetime CLI plus its own local codegen.

Usage

A new app and an app migrating off Convex cloud adopt Lens the same way. Source code does not change; a new app simply skips the data steps.

Starting a new app from one of Convex's templates? create-convex-lens does the steps below for you, and refuses templates that need something Lens cannot run yet (Convex Auth signs JWTs inside the isolate, which needs WebCrypto Lens does not implement; third-party providers such as Clerk are fine):

npm create convex-lens@latest my-app            # react-vite by default
npm create convex-lens@latest my-app -- -t nextjs
npx create-convex-lens@latest --list            # the template shorthands

See packages/create-convex-lens/README.md.

  1. Export your data from the deployment you are leaving: download a backup from the Convex dashboard (Settings, then Backups), or run npx convex export --path snapshot.zip while still on the convex package. Both produce the same snapshot ZIP format.

  2. Install Lens under the convex name through an npm alias, so imports, _generated code, frontend hooks, and tooling keep working unchanged:

    npm i convex@npm:@gravlens/convex

    Component ports install the same way:

    npm i @convex-dev/workflow@npm:@gravlens/workflow

    Ported components: workflow, workpool, batch-worker, crons, rate-limiter, action-retrier.

  3. Push your app to a Lens deployment:

    npx convex dev

    This publishes the module, runs codegen, and writes .env.local (CONVEX_DEPLOYMENT, CONVEX_URL, LENS_SERVER_URL), so a frontend reading VITE_CONVEX_URL/CONVEX_URL picks up the new deployment the same way it picked up the cloud one.

  4. Import the snapshot:

    npx convex import snapshot.zip

    Documents keep their _id and _creationTime; Lens binds the source deployment's table numbers so existing document references keep resolving. Importing into a deployment that already has data takes --replace or --replace-all, with the same confirmation summary as upstream.

  5. Re-create environment variables, which do not travel in the snapshot: npx convex env set OPENAI_API_KEY ... for each one. Auth config (auth.config.ts) lives in your source tree, so it comes along in step 3, and crons begin scheduling on the new deployment automatically because they are code, not data.

What does not transfer yet (the import warns or errors rather than silently dropping data): file storage (_storage entries are skipped with a warning; re-upload files after migrating), components (a snapshot containing _components/ is rejected until Phase 5; ported components start with fresh state), and in-flight scheduled functions (Convex's export does not include _scheduled_functions on either platform).

The migration flow is tested end to end against a real Convex cloud export: the checked-in AI Town snapshot in packages/lens-cli/test/fixtures/aitown-convex-snapshot.zip is imported into a live deployment by packages/lens-cli/test/import_live.test.ts.

Note on versions: each Lens package mirrors the version of the package it stands in for, so a project asking for convex: ^1.44.0 is satisfied by @gravlens/[email protected], and anything that peer-depends on convex installs cleanly. Packages with no upstream counterpart (@gravlens/convex-runtime) carry their own version. Inside this repository, apps consume Lens through pnpm workspace aliases instead of npm (see examples/).

Your data in SpacetimeDB

Every table in your defineSchema is a real SpacetimeDB table of the same name, so it shows up in the SpacetimeDB dashboard and in spacetime sql:

spacetime sql -s <server> <database> 'select id, author, body from messages'

Each top-level field whose validator is a single scalar kind (v.string(), v.id(), v.number(), v.int64(), v.boolean(), v.bytes(), literals, and same-kind unions such as string enums) is a native column, named in the host's snake_case (userId becomes user_id). Nested objects, arrays, v.any(), v.null() and mixed unions live in the table's rest column as JSON. Convex's _id and _creationTime are the id (primary key) and creation_time columns.

SpacetimeDB can add a column to a populated table but never retype or drop one, so a column keeps its kind for the life of the database. The CLI records every column it ever created in lens.columns.json at the project root and only ever appends to it; commit that file with your schema. A field whose validator later changes kind keeps its old column (empty from then on) and its values move into rest, with no migration and no change to what your functions read. When a migration is finished and you want the column to reflect the new kind, npx convex columns rebuild <table> exports the table, recreates it with the current layout across two publishes, and re-imports the documents with their ids preserved (the table is empty in between).

Tables that are not in the schema (schema-optional mode, imports of undeclared tables) and Convex's system tables stay in the _lens_docs table as JSON documents. Table names starting with lens_, _lens_, or st_ are reserved.

Tests

Conformance is oracle-based: tests compare Lens behavior against the real convex npm package, convex-test, and the vendored convex-backend sources. Intentional behavioral differences are recorded in the deviation register.

| Suites | Files | Tests | | |---|---|---|---| | From Convex upstream, running unchanged | 42 | 366 | ✅ passing | | Lens-authored | 80 | 936 | ✅ passing | | Total | 122 | 1,302 | ✅ |

The upstream suites are copied verbatim from the vendored component repositories (workpool, workflow and its example app, rate-limiter, batch-worker, crons, action-retrier); only package plumbing changes (npm aliases, vitest environment), and they execute on the real Lens runtime through the lens-test harness. The Lens-authored suites cover the rest: convex-js API oracles against the real convex package, runtime core units, CLI codegen oracles and live CLI flows, live conformance of the shared scenario handlers on a real SpacetimeDB server vs the convex-test oracle, component conformance and live e2e for every port, and the example apps end to end. Live suites need pnpm dev:server and skip themselves when it is not running.

Benchmarks

Lens is benchmarked with the Convex tutorial chat, deployed unchanged on both stacks and driven through each product's public ConvexHttpClient / ConvexClient. Latency is p50 in milliseconds (lower is better), throughput is calls per second (higher is better), and the fastest number on each row, across all three columns, is bold. The Lens and Spacetime columns note how much faster than Convex they are wherever they win against it. The Spacetime column is the native baseline: the same app functionality written as an idiomatic native SpacetimeDB module (no Convex API, no Lens layer, driven through the native spacetimedb SDK), showing the platform's ceiling. Lens reads sit at the native call floor, Lens writes carry a fraction of a millisecond of runtime overhead on the per-table storage layout, and Lens reactive delivery (one server-side re-materialization pushed to every subscriber) stays 7x to 630x ahead of Convex at every subscriber count and overtakes even the native baseline's per-socket delivery at 1,024+ subscribers. All numbers are one consolidated 2026-08-23 run set on one machine (Windows 11, i9-14900K); full tables with p95s, methodology, and analysis are in BENCHMARKS.md.

A second, system-level benchmark runs the verbatim a16z-infra/ai-town backend (one of the largest open-source Convex apps: a scheduled-action game engine, vector search, crons, an HTTP router) unchanged on both stacks: pnpm bench:aitown locally, pnpm bench:aitown:cloud hosted, pnpm bench:aitown:show for a recorded 8x-world showcase. The game plays identically on both. Under load the stacks split: chat bursts commit 12x to 25x faster on Lens, engine updates reach all subscribers within a millisecond or two at every fan-out, and at 256 concurrent movement writers Convex's write-contention storm starves the game to 0.10 steps/s while Lens processes every input at 20/s. Like the tutorial bench, a third backend bounds the platform itself: the same game rebuilt as an idiomatic native SpacetimeDB module (packages/lens-bench/stdb-aitown-app) with a scheduled-reducer engine loop and no input queue, so an input applies in the transaction that carries it. Full tables and analysis are in BENCHMARKS.md.

Runs can also be recorded: pnpm bench:tutorial -- --record attaches one passive monitor per backend (subscribed through the same public client an app would use, in its own process) and records the raw data it sees: every message arrival, the bench phase markers, and arrival rates, all timestamped. A run lands in packages/lens-bench/recordings/<session>/ as data .json files plus one self-contained combined page that replays the data over a native chat (or town map) view; the session's four pages (chat/AI Town, local/cloud) link each other, and all can be restyled and rebuilt without re-running the benches. Recorded runs carry that one extra subscriber, so published numbers come from unrecorded runs. The committed recordings are hosted at theultdev.github.io/lens (stable links: latest/chat-local.html, latest/chat-cloud.html, latest/aitown-local.html, latest/aitown-cloud.html); every push to master republishes the site with the recordings as-is (nothing is re-benched on CI) plus a freshly built static Vitest report of the whole workspace at /tests/ (pnpm test:html; live suites need the local dev server and are marked skipped there).

Local

Self-hosted, all backends on localhost: the open-source convex-local-backend (SQLite, the self-host default) vs a local SpacetimeDB 2.8.2 server.

Single calls (50 messages of history, p50):

| Operation | Convex | Lens | Spacetime | |---|---|---|---| | Query, cache hit | 0.5 ms | 0.4 ms (1.1x faster) | 0.4 ms (1.1x faster) | | Mutation | 8.0 ms | 0.6 ms (13x faster) | 0.4 ms (20x faster) |

Mutation throughput (sustained):

| Load | Convex | Lens | Spacetime | |---|---|---|---| | 8 independent clients | 267 calls/s | 2447 calls/s (9.2x faster) | 2532 calls/s (9.5x faster) | | 32 independent clients | 925 calls/s | 2787 calls/s (3.0x faster) | 9897 calls/s (11x faster) | | 64 independent clients | 1250 calls/s | 1735 calls/s (1.4x faster) | 17514 calls/s (14x faster) | | 128 independent clients | 1261 calls/s | 1508 calls/s (1.2x faster) | 30444 calls/s (24x faster) | | 256 independent clients | 1104 calls/s | 1381 calls/s (1.3x faster) | 13550 calls/s (12x faster) | | 512 independent clients | 1380 calls/s | 1633 calls/s (1.2x faster) | 14662 calls/s (11x faster) | | One tab, burst of 8 | 129 calls/s | 1154 calls/s (8.9x faster) | 3865 calls/s (30x faster) | | One tab, burst of 32 | 127 calls/s | 2785 calls/s (22x faster) | 15032 calls/s (119x faster) | | One tab, burst of 64 | 65 calls/s | 3111 calls/s (48x faster) | 32013 calls/s (493x faster) | | One tab, burst of 128 | 79 calls/s | 4956 calls/s (62x faster) | 36549 calls/s (460x faster) | | One tab, burst of 256 | 96 calls/s | 3525 calls/s (37x faster) | 60309 calls/s (628x faster) | | One tab, burst of 512 | 107 calls/s | 2991 calls/s (28x faster) | 64814 calls/s (603x faster) |

Interactive streams (N tabs each sending a mutation every ~15 ms, about 65/s per tab; p50 / p95 per mutation):

| Load | Convex | Lens | Spacetime | |---|---|---|---| | 1 streaming tab | 10.2 / 199 ms | 1.9 / 3.4 ms (5.4x faster) | 2.0 / 3.8 ms (5.1x faster) | | 8 streaming tabs | 2,435 / 4,425 ms | 4.4 / 10.6 ms (553x faster) | 5.0 / 16.9 ms (487x faster) | | 32 streaming tabs | 5,948 / 10,611 ms | 62.1 / 156 ms (96x faster) | 7.7 / 17.1 ms (772x faster) |

(Convex saturates at 213 to 446 calls/s against the 520 to 2,100/s offered; the seconds-long latencies are queueing. Lens and Spacetime keep up at every count.)

Cache-miss reads as history grows (query re-executed after a write, p50):

| History | Query:Convex | Query:Lens | Query:Spacetime | Search:Convex | Search:Lens | Search:Spacetime | |---|---|---|---|---|---|---| | 1,200 messages | 82.6 ms | 10.0 ms(8.3x faster) | 0.9 ms(92x faster) | 27.7 ms | 12.2 ms(2.3x faster) | 0.9 ms(31x faster) | | 5,000 messages | 82.1 ms | 4.3 ms(19x faster) | 0.8 ms(103x faster) | 17.3 ms | 15.0 ms(1.2x faster) | 0.8 ms(22x faster) | | 10,000 messages | 128 ms | 5.6 ms(23x faster) | 0.7 ms(183x faster) | 15.3 ms | 13.3 ms(1.2x faster) | 0.8 ms(19x faster) |

Reactive delivery (mutation sent to every subscriber updated, p50):

| Subscribers | Convex | Lens | Spacetime | |---|---|---|---| | 8 | 99.3 ms | 14.3 ms (6.9x faster) | 1.1 ms (90x faster) | | 32 | 110 ms | 9.6 ms (11x faster) | 1.0 ms (110x faster) | | 256 | 1,301 ms | 10.1 ms (129x faster) | 6.7 ms (194x faster) | | 1,024 | 5,139 ms | 12.1 ms (425x faster) | 24.8 ms (207x faster) | | 2,048 | 10,261 ms | 16.4 ms (626x faster) | 68.5 ms (150x faster) |

(A single subscriber among the 2,048 still sees the update in 6.3 ms on Lens vs 111 ms on Convex, and in 1.7 ms on native SpacetimeDB.)

Cloud

Hosted, same client machine, WAN round-trips included: Convex Professional S256 vs SpacetimeDB maincloud (Lens and the native Spacetime baseline both run on maincloud). Both origins sit in North Virginia (maincloud resolves directly to Ashburn servers; Convex runs in AWS us-east-1 behind Cloudflare anycast), so the comparison is geographically fair; raw ping and TCP timings are in BENCHMARKS.md. These tables are the 2026-08-24 re-roll, the first with server-side group commit (concurrent callers' mutations coalesce into shared transactions under pressure). Convex's 128-512-client rows measured well below its best recorded session this time (it has reached 880 to 1,515 calls/s there before; hosted Convex concurrency swings widely run to run) - Lens's 1,268-1,426 calls/s beats both readings at 128 and 256 and sits at 0.94x the best-ever Convex 512 row.

Single calls (50 messages of history, p50):

| Operation | Convex | Lens | Spacetime | |---|---|---|---| | Query, cache hit | 87.2 ms | 61.4 ms (1.4x faster) | 62.3 ms (1.4x faster) | | Mutation | 101 ms | 61.7 ms (1.6x faster) | 57.6 ms (1.8x faster) |

Mutation throughput (sustained):

| Load | Convex | Lens | Spacetime | |---|---|---|---| | 8 independent clients | 75 calls/s | 136 calls/s (1.8x faster) | 145 calls/s (1.9x faster) | | 32 independent clients | 306 calls/s | 512 calls/s (1.7x faster) | 550 calls/s (1.8x faster) | | 64 independent clients | 577 calls/s | 889 calls/s (1.5x faster) | 1066 calls/s (1.8x faster) | | 128 independent clients | 283 calls/s | 1268 calls/s (4.5x faster) | 2108 calls/s (7.5x faster) | | 256 independent clients | 149 calls/s | 1355 calls/s (9.1x faster) | 3857 calls/s (26x faster) | | 512 independent clients | 342 calls/s | 1426 calls/s (4.2x faster) | 7413 calls/s (22x faster) | | One tab, burst of 8 | 34 calls/s | 35 calls/s (1.0x faster) | 150 calls/s (4.4x faster) | | One tab, burst of 32 | 49 calls/s | 418 calls/s (8.6x faster) | 578 calls/s (12x faster) | | One tab, burst of 64 | 59 calls/s | 663 calls/s (11x faster) | 1009 calls/s (17x faster) | | One tab, burst of 128 | 58 calls/s | 1146 calls/s (20x faster) | 2032 calls/s (35x faster) | | One tab, burst of 256 | 59 calls/s | 1254 calls/s (21x faster) | 3463 calls/s (58x faster) | | One tab, burst of 512 | 66 calls/s | 1450 calls/s (22x faster) | 5692 calls/s (86x faster) |

Interactive streams (N tabs each sending a mutation every ~15 ms, about 65/s per tab; p50 / p95 per mutation):

| Load | Convex | Lens | Spacetime | |---|---|---|---| | 1 streaming tab | 138 / 174 ms | 56.7 / 284 ms (2.4x faster) | 80.6 / 108 ms (1.7x faster) | | 8 streaming tabs | 1,005 / 1,828 ms | 60.7 / 71.0 ms (17x faster) | 82.5 / 110 ms (12x faster) | | 32 streaming tabs | 1,475 / 2,621 ms | 372 / 430 ms (4.0x faster) | 84.3 / 113 ms (18x faster) |

(At 32 tabs Convex saturates at 1,254 calls/s against ~2,100/s offered; Lens (2,353/s) and the native baseline (2,360/s) keep up. Lens's dispatch lane is batch-amortized on both sides now: each tab's queue coalesces client-side, and the server's group commit merges concurrent tabs' batches into shared transactions.)

Cache-miss reads as history grows (query re-executed after a write, p50):

| History | Query:Convex | Query:Lens | Query:Spacetime | Search:Convex | Search:Lens | Search:Spacetime | |---|---|---|---|---|---|---| | 1,200 messages | 185 ms | 128 ms(1.4x faster) | 102 ms(1.8x faster) | 210 ms | 139 ms(1.5x faster) | 101 ms(2.1x faster) | | 5,000 messages | 194 ms | 120 ms(1.6x faster) | 104 ms(1.9x faster) | 216 ms | 134 ms(1.6x faster) | 103 ms(2.1x faster) | | 10,000 messages | 185 ms | 122 ms(1.5x faster) | 104 ms(1.8x faster) | 210 ms | 134 ms(1.6x faster) | 104 ms(2.0x faster) |

Reactive delivery (mutation sent to every subscriber updated, p50):

| Subscribers | Convex | Lens | Spacetime | |---|---|---|---| | 8 | 111 ms | 79.9 ms (1.4x faster) | 55.2 ms (2.0x faster) | | 32 | 118 ms | 71.1 ms (1.7x faster) | 54.7 ms (2.2x faster) | | 256 | 1,385 ms | 75.1 ms (18x faster) | 57.0 ms (24x faster) | | 1,024 | 5,329 ms | 78.8 ms (68x faster) | 79.5 ms (67x faster) | | 2,048 | 10,470 ms | 85.8 ms (122x faster) | 141 ms (74x faster) |

(A single subscriber among the 2,048 still sees the update in 72.1 ms on Lens vs 230 ms on Convex, and in 60.9 ms on native SpacetimeDB. The native large-N rows open one real WebSocket per subscriber from one machine; Lens multiplexes one invalidation socket per client process, which is why it can edge native at the very top of the ladder.)

Summary

  • Writes: SpacetimeDB's commitlog commits a Lens mutation in under a millisecond locally; the Convex backend's SQLite path takes about 8 ms. A tab that fires many mutations at once has its queue coalesced into one Lens transaction, so bursts approach 5,000 calls/s locally and reach 1,450 hosted. Convex's client pipelines the burst over its WebSocket session, but the server still executes one session's mutations sequentially, about 34 to 129 calls/s on both targets.
  • Reads: both stacks cache query results server-side, so cache hits sit at each platform's call floor (a near-tie locally, RTT-bound hosted). Lens cache misses stay flat as history grows because index scans are bounded; the Convex backend's misses grow with table size. Cache-miss search re-execution is flat too (12-15 ms vs Convex's 15-28) after the lazy-search round: a .take(n) search fetches n + 1 candidate docs instead of every ranked candidate, and dictionary scans are bounded to the index.
  • Reactivity: a Lens mutation re-executes the subscribed queries it invalidated once, inside its own transaction, and the results ride the same broadcast as the commit. Delivery stays flat in subscriber count: all 2,048 subscribers see an update in 16.4 ms locally and 85.8 ms hosted vs 10.3 to 10.5 s on Convex, and any single subscriber among them sees it at close to single-subscriber latency.
  • Concurrent writers, hosted: this used to be where Convex S256 won (880 vs 647 calls/s at 128 clients, 1,366 vs 687 at 256, in the 2026-08-23 set). Group commit closed it: under sustained pressure independent callers' mutations park in a queue and a shared drain transaction executes the backlog in one pass, so hosted Lens now sustains 1,268 to 1,426 calls/s at 128 to 512 clients - ahead of every recorded hosted Convex row except its best 512 reading (1,515, a 0.94x). The gate that arms it is the module's own measured dispatch cost, so fast local deployments keep the lower-latency always-inline path. The native baseline (2,108 to 7,413 reducer calls/s) still marks the remaining headroom.
  • The native ceiling: the Spacetime column bounds what the platform can do. Lens reads sit at it; writes carry about 0.2 ms of Lens-runtime overhead each (0.6 vs 0.4 ms locally), invisible behind WAN RTT for single hosted calls and visible under sustained concurrency; reactive delivery pays a few ms per update for the server-side re-execution that keeps it one query run regardless of subscriber count (9.6 vs 1.0 ms to all 32 locally), stays flat as subscribers grow, and overtakes native's per-socket delivery from 1,024 subscribers up.

Repository layout

| Path | Contents | |---|---| | packages/lens | The convex-compatible package | | packages/lens-runtime | SpacetimeDB module core: dispatcher, doc store, scheduler, storage, components | | packages/lens-cli | The lens/convex CLI | | packages/lens-test | convex-test-compatible test harness | | packages/lens-helpers | Ported subset of convex-helpers | | packages/lens-stdb-test | Live conformance suite against a real SpacetimeDB server | | packages/lens-bench | Real-world benchmark: tutorial chat on the real Convex backend vs Lens vs a native SpacetimeDB baseline | | packages/components/* | Component ports (@gravlens/*) | | examples/ | Sample apps: Convex tutorial, quickstart, workflow example, AI Town (the a16z-infra/ai-town backend, verbatim) | | vendor/ | Pinned upstream sources used as reference and test oracles | | docs/plan/ | Development plan details (see PLAN.md for the index) |

Development

Requires Node >= 22.18 and pnpm. Works on Windows, Linux, and macOS.

pnpm install
pnpm dev:server   # isolated local SpacetimeDB server on port 3111
pnpm typecheck
pnpm test            # run every suite, one process per package
pnpm test:ui         # Vitest UI in the browser, whole workspace
pnpm test:html       # static HTML test report (reports/vitest/) for CI
pnpm bench           # runtime micro-benchmarks against the live server
pnpm bench:tutorial  # real-world benchmark: Convex vs Lens vs native STDB

pnpm test is the canonical gate: it runs each package's suite in its own process. pnpm test:ui and pnpm test:html run the same suites through the root Vitest config (vitest.config.ts), which registers every package as a Vitest project, so the UI can browse, filter, and re-run any test in the workspace. The HTML report is a static site (serve it with npx vite preview --outDir reports/vitest, or publish it as a CI artifact). Live suites skip themselves when the dev server is not running, so both commands work offline out of the box.

License and attribution

Lens ports convex-js (Apache-2.0) source near-verbatim with attribution; see packages/lens/NOTICE. The backend semantics are reimplemented on SpacetimeDB, not copied. examples/ai-town vendors a16z-infra/ai-town (MIT) verbatim with its license file; see that example's README.