Skip to content
Paykit
Esc
↑↓navigate↵open⌘Jpreview
On this page

make

Billing as Effect atoms: the tenant bindings, shaped for a client.

Source: packages/paykit/src/Atom.ts Kind: Primitive · Product: Usage

Billing as Effect atoms: the tenant bindings, shaped for a client.

make builds one runtime over the layer that reaches your Api — an HTTP client in a browser, the in-process client in a test — and returns the queries and commands a component reads. A successful command invalidates the account’s reactivity key, so entitlements and billing refresh after every write without the caller wiring a subscription. check is a command atom because the amount is part of the question.

The runtime is a normal Atom.AtomRuntime: compose an application query on the same client with billing.runtime.atom(...), and wrap it in billing.runtime.factory.withReactivity([PaykitAtom.accountReactivityKey(account)]) to refresh it with the paykit writes.

Reading an entitlement and tracking usage

import { RegistryProvider, useAtomSet, useAtomValue } from "@effect/atom-react";
import * as PaykitAtom from "@potion.run/paykit/Atom";
import * as AsyncResult from "effect/reactivity/AsyncResult";

// No catalog: `startTrial` and `scheduleTierChange` name a plan, and the
// deployment expands it with the catalog its model was built with.
export const billing = PaykitAtom.make({
  url: "/api/billing",
});

const entitlements = billing.entitlements("acct_1");
const trackUsage = billing.trackUsage("acct_1");

function Usage() {
  const result = useAtomValue(entitlements);
  const view = AsyncResult.isSuccess(result) ? result.value : null;
  const track = useAtomSet(trackUsage, { mode: "promise" });
  return (
    <button onClick={() => track({ feature: "messages", amount: 1, dedupeKey: crypto.randomUUID() })}>
      {view?.features.messages?.remaining ?? "…"} left
    </button>
  );
}

export const App = () => (
  <RegistryProvider>
    <Usage />
  </RegistryProvider>
);

Was this page helpful?