soul v4.2.0

@jun/soul/plans

Plans

Planes y limites tipados, sin magia y sin dependencias.

Que resuelve#

Define que puede hacer cada plan y cuanto. El plan vive en la organizacion —la unidad que paga— y soul solo se encarga de evaluarlo: no cobra. La pasarela de pago es tuya.

Declarar los planes#

TypeScript
import { definePlans } from "@jun/soul/plans";

export const PLANS = definePlans({
  free:    { proyectos: 1,        miembros: 2,  almacenamientoBytes: 50 * 1024 * 1024 },
  pro:     { proyectos: 20,       miembros: 10, almacenamientoBytes: 5 * 1024 * 1024 * 1024 },
  founder: { proyectos: Infinity, miembros: Infinity, almacenamientoBytes: 50 * 1024 * 1024 * 1024 },
});

La API#

definePlans(objeto)
Identidad tipada: devuelve el mismo objeto pero con los tipos inferidos, para que autocompletar conozca tus limites.
planFor(plans, nombrePlan)
Resuelve los limites de una organizacion. Si el nombre no existe, cae al primer plan declarado.
hasCapacity(limite, actual)
actual < limite. Funciona con Infinity.
formatLimit(n)
Para mostrar: Infinity se convierte en "Ilimitado".

Aplicar un limite#

El patron es siempre el mismo: resolver el plan, contar lo que ya hay, y comparar antes de crear.

TypeScript
import { planFor, hasCapacity, formatLimit } from "@jun/soul/plans";
import { PLANS } from "../config";

proyectos.post("/", tenancy.requirePermission("proyectos.write"), async (c) => {
  const limites = planFor(PLANS, c.var.org.plan);

  const { n } = (await c.env.DB.prepare(
    "SELECT COUNT(*) AS n FROM proyectos WHERE org_id = ?",
  ).bind(c.var.org.id).first<{ n: number }>())!;

  if (!hasCapacity(limites.proyectos, n)) {
    return c.html(errorPage(
      "Alcanzaste el limite de tu plan",
      `Tu plan permite ${formatLimit(limites.proyectos)} proyecto(s). Mejora el plan para crear mas.`,
    ), 403);
  }

  // ...crear
});