Dokumentation

Plan-Limits

Serverseitige Durchsetzung von Plan-Limits mit dem Billing-Katalog und checkLimit().

Öffnen inChatGPT (öffnet in neuem Tab)Claude (öffnet in neuem Tab)Cursor (öffnet in neuem Tab)Serverseitige Durchsetzung von Plan-Limits mit dem Billing-Katalog und checkLimit().

Motoko Base definiert numerische Plan-Limits in src/lib/billing/config/plans.ts (PLAN_CATALOG). Die Usage-Einstellungsseite zeigt den Fortschritt gegenüber diesen Limits. Die Durchsetzung erfolgt serverseitig in Feature-Mutationen — niemals allein in der UI.

Architecture

PLAN_CATALOG.limits
   ↓
getBillingSummary(userId) → summaryGetLimit()
   ↓
checkLimit(userId, limitId, currentUsage, delta)
   ↓
Server Action / route handler rejects or allows mutation

Limits werden aus der Billing-Zusammenfassung des Nutzers aufgelöst. Nutzer ohne bezahlten Zugang (einschließlich past_due) erhalten Free-Tier-Limits. Wenn Polar nicht konfiguriert ist, erlaubt checkLimit() Mutationen, damit lokale Entwicklung mit Minimal-Konfiguration weiter funktioniert.

checkLimit API

import { checkLimit } from "@/lib/billing";

const result = await checkLimit(userId, "links", currentCount, 1);

if (result.status === "limit_reached") {
  // reject mutation with user-facing message
}

if (result.status === "unavailable") {
  // billing lookup failed — fail closed with safe error
}

Ergebnisse:

StatusMeaning
allowedMutation darf fortgesetzt werden (limit: null = unbegrenzt)
limit_reachedWürde das Plan-Limit überschreiten
unavailableBilling-Zusammenfassung konnte nicht aufgelöst werden

Shipped enforcement

FeatureBoundaryNotes
LinkscreateLinkActionUpdate/Delete werden nicht am Limit blockiert
StoragecreateUploadAction + confirmUploadActionVorabprüfung beim Init; maßgebliche Prüfung bei Confirm (Presigned-Upload-Lebenszyklus)

Storage-Nutzung zählt files-Zeilen mit status = "ready" und summiert sizeBytes. Ausstehende Uploads sind nur bei der Init-Vorabprüfung enthalten.

Usage metrics registry

Die Nutzungsanzeige (src/lib/billing/usage.ts) liest Metriken aus einer Registry. Jedes Feature registriert seine Messung:

// src/features/links/usage-metric.ts
registerUsageMetric({
  id: "links",
  limitId: "links",
  label: "Links",
  measure: countLinksForUser,
});

Importiere @/features/dashboard/lib/bootstrap-usage-metrics (oder die einzelnen usage-metric.ts-Module), bevor du getUsageSnapshot() aufrufst, damit die Metriken registriert sind.

Demo entfernen: Lösche den Feature-Ordner und dessen usage-metric.ts. Eine Bearbeitung von src/lib/billing/usage.ts ist nicht erforderlich.

Adding limits to a new feature

  1. Füge eine LimitId und einen Limit-Wert zu PLAN_CATALOG in config/plans.ts hinzu.
  2. Exportiere eine measure(userId)-Funktion aus deinen Feature-Queries.
  3. Registriere die Metrik in <feature>/usage-metric.ts.
  4. Rufe checkLimit() in der Server-Mutation vor dem Persistieren neuen quota-verbrauchenden Zustands auf.
  5. Übergebe die current-Nutzung als Parameter — hardcode keine Plan-Limits im Feature.

Concurrency

Links und Storage verwenden best-effort-Durchsetzung: Zwei gleichzeitige Creates nahe am Limit können kurzzeitig beide erfolgreich sein. Das ist für den Starter akzeptabel und ehrlich dokumentiert. Strikte Durchsetzung würde Transaktionen oder pro-Nutzer-Sperren erfordern — füge das in deinem Produkt hinzu, wenn du harte Garantien brauchst.

Gleichzeitige Storage-Uploads folgen dem gleichen Modell: Die maßgebliche Prüfung läuft bei Confirm/Finalisierung, aber zwei Confirms nahe am Limit können unter Race-Bedingungen beide durchgehen.

  • Payments (Polar) — Billing-Zusammenfassung und Entitlements
  • Storage — Upload-Lebenszyklus
  • src/lib/security/README.md — Sicherheits-Defaults