store

package
v1.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 10 Imported by: 2

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Store

type Store interface {
	// Plan methods
	CreatePlan(ctx context.Context, p *plan.Plan) error
	GetPlan(ctx context.Context, planID id.PlanID) (*plan.Plan, error)
	GetPlanBySlug(ctx context.Context, slug string, appID string) (*plan.Plan, error)
	ListPlans(ctx context.Context, appID string, opts plan.ListOpts) ([]*plan.Plan, error)
	UpdatePlan(ctx context.Context, p *plan.Plan) error
	DeletePlan(ctx context.Context, planID id.PlanID) error
	ArchivePlan(ctx context.Context, planID id.PlanID) error

	// Feature catalog methods
	CreateFeature(ctx context.Context, f *feature.Feature) error
	GetFeature(ctx context.Context, featureID id.FeatureID) (*feature.Feature, error)
	GetFeatureByKey(ctx context.Context, key string, appID string) (*feature.Feature, error)
	ListFeatures(ctx context.Context, appID string, opts feature.ListOpts) ([]*feature.Feature, error)
	ListGlobalFeatures(ctx context.Context, opts feature.ListOpts) ([]*feature.Feature, error)
	UpdateFeature(ctx context.Context, f *feature.Feature) error
	DeleteFeature(ctx context.Context, featureID id.FeatureID) error
	ArchiveFeature(ctx context.Context, featureID id.FeatureID) error

	// Subscription methods
	CreateSubscription(ctx context.Context, s *subscription.Subscription) error
	GetSubscription(ctx context.Context, subID id.SubscriptionID) (*subscription.Subscription, error)
	GetActiveSubscription(ctx context.Context, tenantID string, appID string) (*subscription.Subscription, error)
	ListSubscriptions(ctx context.Context, tenantID string, appID string, opts subscription.ListOpts) ([]*subscription.Subscription, error)
	UpdateSubscription(ctx context.Context, s *subscription.Subscription) error
	// CancelSubscription cancels a subscription and returns the cancel_at it
	// stored, in UTC. Immediately, it sets cancel_at and canceled_at to now
	// and the status to canceled. Otherwise it schedules the cancel for the
	// end of the period that is current when the write lands: the statement
	// copies current_period_end into cancel_at from the row itself, never a
	// value the caller read, so a period the lifecycle clock advanced in
	// between is the one that ends. A scheduled cancel never changes the
	// status; the clock's EnactSubscriptionCancel does, once cancel_at has
	// passed. Both refuse a subscription that is already canceled or expired,
	// with ledger.ErrSubscriptionCanceled or ledger.ErrSubscriptionExpired, in
	// the same conditional write, so an operator's cancel racing the clock's
	// enactment never cancels a subscription twice.
	CancelSubscription(ctx context.Context, subID id.SubscriptionID, immediately bool) (time.Time, error)

	// Operator writes that can race the lifecycle clock. Each one writes only
	// the columns it names in one conditional statement whose WHERE repeats
	// the engine's precondition, never a whole row read earlier, so it cannot
	// put back a period the clock advanced or revive a subscription the clock
	// canceled. The bool ones report whether a row matched; a missing row is
	// false with no error, and the caller re-reads to learn why.
	//
	// PauseSubscription sets status to paused and paused_at to at when the
	// status is active or trialing.
	PauseSubscription(ctx context.Context, subID id.SubscriptionID, at time.Time) (bool, error)
	// ResumeSubscription applies r when the status is paused and paused_at
	// still equals r.PausedAt (is null when r.PausedAt is nil): it sets the
	// status to r.Status, current_period_end to r.PeriodEnd unless that is
	// nil (moving cancel_at with it when cancel_at equals the old
	// current_period_end), trial_end to r.TrialEnd unless that is nil, the
	// four stretch columns to r.Stretch unless that is nil, and clears
	// paused_at. current_period_start never changes. The lifecycle clock
	// never writes a paused row's period or trial, so nothing it does can be
	// lost here.
	ResumeSubscription(ctx context.Context, subID id.SubscriptionID, r subscription.Resume) (bool, error)
	// ChangeSubscriptionPlan sets plan_id and quantity when the status is
	// neither canceled nor expired. A nil quantity is stored as empty.
	ChangeSubscriptionPlan(ctx context.Context, subID id.SubscriptionID, planID id.PlanID, quantity map[string]int64) (bool, error)
	// SetSubscriptionProvider records the provider's id and name for a
	// subscription, or returns ledger.ErrSubscriptionNotFound.
	SetSubscriptionProvider(ctx context.Context, subID id.SubscriptionID, providerID, providerName string) error

	// Meter methods
	IngestBatch(ctx context.Context, events []*meter.UsageEvent) error
	Aggregate(ctx context.Context, tenantID, appID, featureKey string, period plan.Period) (int64, error)
	AggregateMulti(ctx context.Context, tenantID, appID string, featureKeys []string, period plan.Period) (map[string]int64, error)
	QueryUsage(ctx context.Context, tenantID, appID string, opts meter.QueryOpts) ([]*meter.UsageEvent, error)
	PurgeUsage(ctx context.Context, before time.Time) (int64, error)

	// Entitlement methods
	GetCached(ctx context.Context, tenantID, appID, featureKey string) (*entitlement.Result, error)
	SetCached(ctx context.Context, tenantID, appID, featureKey string, result *entitlement.Result, ttl time.Duration) error
	Invalidate(ctx context.Context, tenantID, appID string) error
	InvalidateFeature(ctx context.Context, tenantID, appID, featureKey string) error

	// Invoice methods
	CreateInvoice(ctx context.Context, inv *invoice.Invoice) error
	GetInvoice(ctx context.Context, invID id.InvoiceID) (*invoice.Invoice, error)
	ListInvoices(ctx context.Context, tenantID, appID string, opts invoice.ListOpts) ([]*invoice.Invoice, error)
	UpdateInvoice(ctx context.Context, inv *invoice.Invoice) error
	GetInvoiceByPeriod(ctx context.Context, tenantID, appID string, periodStart, periodEnd time.Time) (*invoice.Invoice, error)
	ListPendingInvoices(ctx context.Context, appID string) ([]*invoice.Invoice, error)
	MarkInvoicePaid(ctx context.Context, invID id.InvoiceID, paidAt time.Time, paymentRef string) error
	MarkInvoiceVoided(ctx context.Context, invID id.InvoiceID, reason string) error
	// SetInvoiceProvider records the provider's id and name for an invoice,
	// and nothing else, so a provider sync cannot put back a status the
	// lifecycle clock or an operator wrote meanwhile. It returns
	// ledger.ErrInvoiceNotFound for a missing invoice.
	SetInvoiceProvider(ctx context.Context, invID id.InvoiceID, providerID, providerName string) error

	// Coupon methods
	CreateCoupon(ctx context.Context, c *coupon.Coupon) error
	GetCoupon(ctx context.Context, code string, appID string) (*coupon.Coupon, error)
	GetCouponByID(ctx context.Context, couponID id.CouponID) (*coupon.Coupon, error)
	ListCoupons(ctx context.Context, appID string, opts coupon.ListOpts) ([]*coupon.Coupon, error)
	UpdateCoupon(ctx context.Context, c *coupon.Coupon) error
	DeleteCoupon(ctx context.Context, couponID id.CouponID) error
	// ApplyCoupon and IncrementCouponRedemptions are low-level operations
	// kept for backends and tests that need them separately. Engine code
	// redeems through RedeemCoupon, which performs both as one unit.
	ApplyCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
	ListAppliedCoupons(ctx context.Context, subID id.SubscriptionID) ([]*coupon.Coupon, error)
	IncrementCouponRedemptions(ctx context.Context, couponID id.CouponID) error
	// RedeemCoupon records a coupon's application to a subscription and
	// increments its redemption count as a single unit: either both land or
	// neither does. The redemption cap (MaxRedemptions) is enforced by a
	// conditional increment inside that unit, not by a read followed by a
	// separate write, so it holds under concurrent callers racing the same
	// coupon toward its cap. A MaxRedemptions of zero or less means
	// unlimited on every backend.
	RedeemCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error

	// Lifecycle clock methods. The two List methods find the rows a clock
	// step has to visit, across every app when AppID is empty. Each
	// transition changes one row in a single conditional write that repeats
	// its precondition, touches only the columns it names, and reports
	// whether a row matched, so two replicas running the clock at once never
	// apply a transition twice and never overwrite an operator's concurrent
	// write to other columns. A missing row is false with no error.
	ListDueSubscriptions(ctx context.Context, opts subscription.DueOpts) ([]*subscription.Subscription, error)
	ListOverdueInvoices(ctx context.Context, opts invoice.OverdueOpts) ([]*invoice.Invoice, error)
	// EndSubscriptionTrial makes a trialing subscription whose trial_end is at
	// or before now active.
	EndSubscriptionTrial(ctx context.Context, subID id.SubscriptionID, now time.Time) (bool, error)
	// EnactSubscriptionCancel cancels a subscription whose cancel_at is at or
	// before now and that is not already canceled or expired, and sets
	// canceled_at to cancel_at, the moment the cancellation took effect.
	EnactSubscriptionCancel(ctx context.Context, subID id.SubscriptionID, now time.Time) (bool, error)
	// AdvanceSubscriptionPeriod sets the current period to [start, end) when
	// the subscription is active, trialing or past due, its current period
	// still ends at from (the end the caller listed and worked from) and
	// that end is at or before now, end is later than it, and no
	// cancellation falls at or before it. Pinning from means a replica that
	// listed the row before another replica moved it matches nothing, so it
	// cannot announce the periods the other one already announced. from
	// round-trips from the row, so the equality holds on every backend,
	// sqlite's text timestamps included.
	AdvanceSubscriptionPeriod(ctx context.Context, subID id.SubscriptionID, from, start, end, now time.Time) (bool, error)
	// MarkInvoicePastDue moves a pending invoice whose due_date is before now
	// to past_due.
	MarkInvoicePastDue(ctx context.Context, invID id.InvoiceID, now time.Time) (bool, error)

	// Core methods
	Migrate(ctx context.Context) error
	Ping(ctx context.Context) error
	Close() error
}

Store is the unified storage interface for all Ledger entities. Instead of embedding the sub-interfaces, we explicitly declare all methods to avoid naming conflicts.

Directories

Path Synopsis
Package mongo provides a MongoDB Store implementation for Ledger using Grove ORM.
Package mongo provides a MongoDB Store implementation for Ledger using Grove ORM.
Package postgres provides a PostgreSQL Store implementation for Ledger using Grove ORM.
Package postgres provides a PostgreSQL Store implementation for Ledger using Grove ORM.
Package sqlite provides a SQLite Store implementation for Ledger using Grove ORM.
Package sqlite provides a SQLite Store implementation for Ledger using Grove ORM.
Package storetest provides a conformance suite every store.Store implementation must pass, plus constructors for each backend.
Package storetest provides a conformance suite every store.Store implementation must pass, plus constructors for each backend.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL