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

@splitch/convex

v0.2.0

Published

First-party Convex Component for synced local Splitch evaluation

Readme

@splitch/convex

The first-party Convex Component for local Splitch Flag evaluation. Configuration is synced into component-private tables, so a Query can resolve a Flag with no network access, and a Mutation can put the resulting Exposure into a transactional outbox alongside your application writes. It installs @splitch/sdk, which owns the shared local evaluator and contract types.

Install

npm install @splitch/convex

Peers: convex >= 1.43, and react 18 or 19 if you use @splitch/convex/react.

Mount the component

// convex/convex.config.ts
import splitch from "@splitch/convex/convex.config.js";
import { defineApp } from "convex/server";
import { v } from "convex/values";

const app = defineApp({ env: { SPLITCH_API_KEY: v.string() } });

app.use(splitch, {
  httpPrefix: "/integrations/splitch/",
  env: { SPLITCH_API_KEY: app.env.SPLITCH_API_KEY },
});

export default app;

Keep the API Key in Convex environment variables. It stays in the deployment environment and must never reach browser code. The configuration callback is served at /integrations/splitch/configuration.

The component also accepts an optional SPLITCH_ENDPOINT to point at a non-production splitch edge; it defaults to https://edge.splitch.dev.

Install from an Action

Call flags.install(ctx) from an Action after mounting the component, and again after upgrading @splitch/convex. The request is idempotent. On upgrade it starts one bounded adoption chain for Exposure delivery work created by the previous version, drains legacy Metric Event rows without accepting new ones, resumes stale configuration sync, and schedules retention for retained rows.

The API Key you mount needs the data-plane:evaluate scope. Metric Events use a separate data-plane:write Key at the direct @splitch/sdk call site.

splitch api-keys create --env production \
  --body-json '{"scopes":["data-plane:evaluate"]}'
// convex/setup.ts
import { Splitch } from "@splitch/convex";
import { components } from "./_generated/api";
import { action } from "./_generated/server";

const flags = new Splitch(components.splitch);

export const install = action({ args: {}, handler: (ctx) => flags.install(ctx) });

Evaluate

Bind the component once with the Splitch class, then use it from Queries and Mutations.

| Method | Context | Returns | Fires an Exposure | | ----------------- | ----------------- | ------------------------ | ----------------- | | peekVariant | Query or Mutation | the Variant value | no | | peekDetails | Query or Mutation | full ResolutionDetails | no | | evaluate | Mutation | the Variant value | yes | | evaluateDetails | Mutation | full ResolutionDetails | yes |

The Exposure an Exposure-bearing call queues is discarded if the caller's transaction rolls back, which is why evaluate is Mutation-only: record it where the Variant is actually encountered, alongside the write it controls.

// convex/flags.ts
import { Splitch } from "@splitch/convex";
import { components } from "./_generated/api";
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";

const flags = new Splitch(components.splitch);

export const checkoutEnabled = query({
  args: { targetingKey: v.string() },
  returns: v.boolean(),
  handler: (ctx, args) =>
    flags.peekVariant(ctx, "new-checkout", { targetingKey: args.targetingKey }, false),
});

export const completeCheckout = mutation({
  args: { targetingKey: v.string(), idempotencyKey: v.string() },
  returns: v.boolean(),
  handler: async (ctx, args) => {
    const enabled = await flags.evaluate(
      ctx,
      "new-checkout",
      { targetingKey: args.targetingKey, idempotencyKey: args.idempotencyKey },
      false,
    );
    if (typeof enabled !== "boolean") throw new Error("new-checkout must be boolean");
    return enabled;
  },
});

idType defaults to user and attributes to {}. The fourth argument is the Default Variant this call falls back to; a resolution that could not be computed reports reason: "ERROR" with its errorCode rather than returning a plausible value.

Metric Events

Send Metric Events with @splitch/sdk from an Action or HTTP Action. Metric Event transport is not application state, so this component does not copy Metric Events, delivery claims, or event history through the consumer's Convex deployment.

React

Component functions are private to the Convex backend, so expose an app-owned public Query that performs your authentication and derives the Evaluation Context server-side. Never take the targeting key from the client.

// convex/flags.ts
export const resolve = query({
  args: { flagKey: v.string(), defaultValue: v.any() },
  returns: v.any(),
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Authentication is required to evaluate Flags");
    return flags.peekDetails(
      ctx,
      args.flagKey,
      { targetingKey: identity.tokenIdentifier },
      args.defaultValue,
    );
  },
});

Bind that generated public Query once, then use the hooks under Convex's existing provider:

import { createSplitchReact } from "@splitch/convex/react";
import { api } from "../convex/_generated/api";

const { useFlag, useFlagDetails } = createSplitchReact(api.flags.resolve);

export function Checkout() {
  const enabled = useFlag("new-checkout", false);
  if (enabled === undefined) return <p>Loading…</p>;
  return enabled ? <NewCheckout /> : <CurrentCheckout />;
}

Both hooks preserve Convex's undefined loading state and update reactively when the synced snapshot changes. They are non-exposing reads: record the Exposure with flags.evaluate() in the Mutation where the Variant is encountered.

Operate it

These four are Action-only:

| Call | What it does | | ------------------------- | -------------------------------------------------------------------- | | flags.install(ctx) | Register (or repair) the installation. Idempotent; rerun on upgrade. | | flags.sync(ctx) | Force a configuration pull; returns the applied Environment version. | | flags.rotateSecret(ctx) | Mint a new push secret for the configuration callback. | | flags.uninstall(ctx) | Revoke the remote installation, then purge component-private state. |

flags.deleteEntity(ctx, { targetingKey, idType }) inside a Mutation removes one Entity's local holdovers and suppresses its queued Exposures.

Background recovery is activity-driven. Configuration nudges and new Exposure outbox rows schedule one installation-scoped batch drainer, which stops once the work is complete. Retained claims and terminal Exposure rows share one scheduled cleanup job set for the earliest expiry. The component registers no cron jobs and invokes nothing periodically while idle.

When to use @splitch/sdk instead

Queries and Mutations cannot call fetch, which is why this component exists. Use @splitch/sdk directly from an Action or HTTP Action when a request-time round-trip is what you actually want, or to mint Precomputed Evaluations for SSR hydration. See https://splitch.dev/docs/sdk/convex.

The SDK's track() posts directly to Splitch's Cloudflare ingestion path and throws on rejection. Do not wrap it in a default Convex Mutation transport because that recreates per-event Convex reads, writes, scheduled functions, and transfer.

Links