130 lines · 4.1 KB
import type { MeterId } from "./meters";

/**
 * Plans are config-driven: `METERING_PLANS` (JSON, same shape as DEFAULT_PLANS) overrides these defaults.
 * `included` is free per period; usage past it is billed (paid plans) or blocked (hard caps).
 * `hardCaps` are enforced atomically by reserveUsage; every other meter is soft and only alerts.
 */
export type PlanId = "scratch" | "free" | "pro";

export type Plan = {
  id: PlanId;
  label: string;
  included: Partial<Record<MeterId, number>>;
  hardCaps: Partial<Record<MeterId, number>>;
  /** Whether overage is billed; when false every included amount acts as a soft cap with alerts. */
  billsOverage: boolean;
};

const GB = 1024 ** 3;
const HOUR = 3600;

export const DEFAULT_PLANS: Record<PlanId, Plan> = {
  scratch: {
    id: "scratch",
    label: "Scratch (anonymous)",
    included: { "storage.sites": 0.1 * GB, "compute.site_requests": 10_000, "network.sites": 1 * GB },
    hardCaps: {
      "storage.sites": 0.1 * GB,
      "compute.site_requests": 10_000,
      "network.sites": 1 * GB,
      "ai.calls": 0,
      "compute.editor": 0,
    },
    billsOverage: false,
  },
  free: {
    id: "free",
    label: "Free",
    included: {
      "storage.artifacts": 5 * GB,