İçeriğe geç

Başlarken

Bu sayfadaki kod docs/src/snippets/getting-started.test.ts dosyasından gelir ve bun run test ile çalıştırılır; sayfa çalışmayan kod gösteremez.

Terminal window
bun add pricemeter zod # ya da npm i / pnpm add

zod zorunlu değil: boyut ve bağlam şemaları için herhangi bir Standard Schema kütüphanesi (zod, valibot, arktype) ya da hiç şema kullanılabilir. pricemeter’in çalışma zamanı bağımlılığı yoktur; ESM + CJS olarak gelir, Node ≥ 20, Bun ve Workers’ta çalışır.

docs/src/snippets/getting-started.test.ts
import { buildMetering, type Rate } from "pricemeter";
import { memoryAdapters } from "pricemeter/testing";
import { z } from "zod";
// $0.02 per SMS; from the 10,000th message on, $0.015. Amounts are integer micro-units.
const sms: Rate = {
id: "sms-2026",
model: "graduated",
tiers: [
{ from: 0, unitPriceMicroUsd: 20_000 },
{ from: 10_000, unitPriceMicroUsd: 15_000 },
],
};
// getRate + commit kept in memory: prepaid, "acme" starts with $10.
const mem = memoryAdapters({ rates: { "otp/sms": sms }, prepaid: true, balances: { acme: 10_000_000 } });
const metering = buildMetering()
.context(z.object({ accountId: z.string() }))
.refs(["otp_send"])
.meter("otp/sms", { country: z.string().length(2) })
.bind(mem); // same as .getRate(mem.getRate).commit(mem.commit)
const ctx = { accountId: "acme" };
  1. Tarife (Rate) tüm kademeleri taşır. Bir gözlemin hangi kademeye düştüğüne rate() karar verir; tutarlar tamsayı mikro birimdir (20_000 = $0,02).

  2. memoryAdapters (pricemeter/testing) test ve örnekler için bellek içi bir getRate + commit verir: usedSoFar’ı kendi yazdığı satırlardan sayar, prepaid: true ile bakiye yetmezse InsufficientCredit fırlatır.

  3. buildMetering() tek bir zincirdir; build() adımı yoktur. .bind(mem) katalogu bu adapter’larla bağlar; üretimde aynı yere kendi getRate ve commit fonksiyonlarını verirsin.

docs/src/snippets/getting-started.test.ts
const r = await metering.observe("otp/sms", { country: "TR" }, 2, { type: "otp_send", id: "msg_1" }, ctx);
// → { ok: true, charged: 40000, lines: [{ meter: "otp/sms", quantity: 2, amount: 40000, tierIndex: 0, … }] }
mem.store.available("acme"); // 9_960_000
mem.store.usage.length; // 1 usage row (refId "otp_send:msg_1:otp/sms")
mem.store.ledger.length; // 1 ledger row (a "charge" of 40000)

observe sırayla şunları yapar: bağlamı ve boyutları doğrular, getRate’ten tarifeyi ve usedSoFar’ı alır, price() ile tutarı hesaplar, planı commit’e verir ve sonucu döner. Plan iki listedir: parayı hareket ettiren ledger satırları ve kullanımı kaydeden usage satırları.

docs/src/snippets/getting-started.test.ts
// A retry with the same ref computes the same plan; commit sees (refType, refId) again and writes nothing.
await metering.observe("otp/sms", { country: "TR" }, 2, { type: "otp_send", id: "msg_1" }, ctx);
mem.store.available("acme"); // still 9_960_000

Her satırın refId’si ref’ten türetilir (otp_send:msg_1:otp/sms). commit aynı (refType, refId)’yi ikinci kez gördüğünde hiçbir şey yazmaz. Kütüphane bunun retry olduğunu bilmez; sonuç yine charged: 40000 der — “bu çağrının fiyatı”, “yazılan tutar” değil (A99) — ama bakiye değişmez.

docs/src/snippets/getting-started.test.ts
await metering.observe("otp/sms", { country: "TUR" }, 1, { type: "otp_send", id: "msg_2" }, ctx);
// → { ok: false, reason: "invalid_dims", meter: "otp/sms", cause: [...] } (zod: length 2)
await metering.observe("otp/sms", { country: "TR" }, 1_000, { type: "otp_send", id: "msg_3" }, ctx);
// → { ok: false, reason: "insufficient_credit", … } ($20 > $9.96; nothing written)
mem.store.setRate("otp/sms", undefined); // the price table no longer has this meter
await metering.observe("otp/sms", { country: "TR" }, 1, { type: "otp_send", id: "msg_4" }, ctx);
// → { ok: false, reason: "no_price", meter: "otp/sms" }

{ ok: false, reason } döndüğünde hiçbir şey yazılmamıştır. Bakiye kontrolü kütüphanede değil commit’in içindedir: yalnızca ledger “bakiye ≥ tutar” kontrolünü yazımla aynı atomik adımda yapabilir. Tüm nedenler: observe ve hold → Result.

docs/src/snippets/getting-started.test.ts
const { result, plan } = await metering.plan.observe("otp/sms", { country: "TR" }, 3, { type: "otp_send", id: "msg_5" }, ctx);
// result.charged → 60000
// plan.ledger → [{ op: "charge", account: "acme", amount: 60000, refType: "otp_send", refId: "otp_send:msg_5:otp/sms", at }]
// plan.usage → [{ account: "acme", meter: "otp/sms", dims: { country: "TR" }, quantity: 3, amount: 60000, rateId: "sms-2026", … }]

metering.plan.* aynı imzalarla çalışır, commit çağırmaz ve { result, plan, lines } döner. Test yazarken ya da planı başka bir yerde (ör. bir Durable Object içinde) uygulamak istediğinde kullanılır.