/testing
// import { expectPlan, memoryAdapters } from "pricemeter/testing";const mem = memoryAdapters({ rates: { "otp/sms": (dims) => (dims.country === "TR" ? flat(20_000) : null) }, // a Rate, or a function of (dims, ctx, at) prepaid: true, balances: { acme: 1_000_000 }, periodOf: (at) => monthKey({ at }), // usedSoFar per (account, meter, month)});const metering = buildMetering().refs(["otp_send"]).meter("otp/sms", ["country"]).bind(mem);
const { plan } = await metering.plan.observe("otp/sms", { country: "TR" }, 2, { type: "otp_send", id: "m1" }, { accountId: "acme" }, { at });expectPlan(plan, { ledger: [{ op: "charge", amount: 40_000 }], // only the fields you give are compared… usage: [{ meter: "otp/sms", dims: { country: "TR" } }], // …but the row count must match});
mem.store.available("acme"); // 1_000_000 — plan.* never commitsmem.store.used("acme", "otp/sms", at); // 0pricemeter/testing gives tests, examples and these docs three things.
memoryAdapters(opts)
Section titled “memoryAdapters(opts)”An in-memory getRate + commit + store that follows the adapter contract: all or nothing, idempotent on (refType, refId), InsufficientCredit when prepaid and short.
| Option | Meaning | Default |
|---|---|---|
rates |
meter → a Rate or (dims, ctx, at) => Rate | { rate, usedSoFar?, carry? } | null. Meters not listed have no price |
{} |
prepaid |
Balance gate | false (postpaid) |
balances |
Account → starting balance (micro-units) | {} |
periodOf |
at → period key; usedSoFar is counted per (account, meter, period) |
one lifetime period "*" |
When an answer has no usedSoFar, getRate fills it from the usage rows it wrote (per meter, without dimensions; A100). store exposes usage, ledger, account(id) ({ balance, reserved }), available(id), used(account, meter, period?), credit(account, amount), setRate(meter, rate?) and commits (the number of commit calls, no-op retries included).
expectPlan(plan, expected)
Section titled “expectPlan(plan, expected)”A framework-free plan assertion: rows are compared in order and partially (only the fields you give), but the row count must match. It throws at the first difference, showing the actual row; it works under vitest, jest, node:test or bun test.
commitContract({ make, prepaid? })
Section titled “commitContract({ make, prepaid? })”The contract every commit must pass: writing rows, a repeated (refType, refId) being a no-op, a negative charge crediting, hold/capture/release accounting, release after a retried hold, and — when prepaid — insufficient balance writing nothing. Returns { ok, checks: [{ name, ok, error? }] }. Usage: Writing adapters.
How you’d do it without this
Section titled “How you’d do it without this”In tests you can use your real adapter: for SQLite, new DatabaseSync(":memory:") is fast and gives the same results as pricemeter/sqlite. Instead of expectPlan, your framework’s toMatchObject works (check the row count separately). The memory adapter’s real value is wiring a tariff to a counter without writing getRate, and being the shortest readable reference for the contract.