Documentation
¶
Overview ¶
Package sqlite provides a SQLite Store implementation for Ledger using Grove ORM.
Index ¶
- Variables
- type Store
- func (s *Store) AdvanceSubscriptionPeriod(ctx context.Context, subID id.SubscriptionID, from, start, end, at time.Time) (bool, error)
- func (s *Store) Aggregate(ctx context.Context, tenantID, appID, featureKey string, period plan.Period) (int64, error)
- func (s *Store) AggregateMulti(ctx context.Context, tenantID, appID string, featureKeys []string, ...) (map[string]int64, error)
- func (s *Store) ApplyCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
- func (s *Store) ArchiveFeature(ctx context.Context, featureID id.FeatureID) error
- func (s *Store) ArchivePlan(ctx context.Context, planID id.PlanID) error
- func (s *Store) CancelSubscription(ctx context.Context, subID id.SubscriptionID, immediately bool) (time.Time, error)
- func (s *Store) ChangeSubscriptionPlan(ctx context.Context, subID id.SubscriptionID, planID id.PlanID, ...) (bool, error)
- func (s *Store) Close() error
- func (s *Store) CreateCoupon(ctx context.Context, c *coupon.Coupon) error
- func (s *Store) CreateFeature(ctx context.Context, f *feature.Feature) error
- func (s *Store) CreateInvoice(ctx context.Context, inv *invoice.Invoice) error
- func (s *Store) CreatePlan(ctx context.Context, p *plan.Plan) error
- func (s *Store) CreateSubscription(ctx context.Context, sub *subscription.Subscription) error
- func (s *Store) DB() *grove.DB
- func (s *Store) DeleteCoupon(ctx context.Context, couponID id.CouponID) error
- func (s *Store) DeleteFeature(ctx context.Context, featureID id.FeatureID) error
- func (s *Store) DeletePlan(ctx context.Context, planID id.PlanID) error
- func (s *Store) EnactSubscriptionCancel(ctx context.Context, subID id.SubscriptionID, at time.Time) (bool, error)
- func (s *Store) EndSubscriptionTrial(ctx context.Context, subID id.SubscriptionID, at time.Time) (bool, error)
- func (s *Store) GetActiveSubscription(ctx context.Context, tenantID, appID string) (*subscription.Subscription, error)
- func (s *Store) GetCached(ctx context.Context, tenantID, appID, featureKey string) (*entitlement.Result, error)
- func (s *Store) GetCoupon(ctx context.Context, code, appID string) (*coupon.Coupon, error)
- func (s *Store) GetCouponByID(ctx context.Context, couponID id.CouponID) (*coupon.Coupon, error)
- func (s *Store) GetFeature(ctx context.Context, featureID id.FeatureID) (*feature.Feature, error)
- func (s *Store) GetFeatureByKey(ctx context.Context, key, appID string) (*feature.Feature, error)
- func (s *Store) GetInvoice(ctx context.Context, invID id.InvoiceID) (*invoice.Invoice, error)
- func (s *Store) GetInvoiceByPeriod(ctx context.Context, tenantID, appID string, periodStart, periodEnd time.Time) (*invoice.Invoice, error)
- func (s *Store) GetPlan(ctx context.Context, planID id.PlanID) (*plan.Plan, error)
- func (s *Store) GetPlanBySlug(ctx context.Context, slug, appID string) (*plan.Plan, error)
- func (s *Store) GetSubscription(ctx context.Context, subID id.SubscriptionID) (*subscription.Subscription, error)
- func (s *Store) IncrementCouponRedemptions(ctx context.Context, couponID id.CouponID) error
- func (s *Store) IngestBatch(ctx context.Context, events []*meter.UsageEvent) error
- func (s *Store) Invalidate(ctx context.Context, tenantID, appID string) error
- func (s *Store) InvalidateFeature(ctx context.Context, tenantID, appID, featureKey string) error
- func (s *Store) ListAppliedCoupons(ctx context.Context, subID id.SubscriptionID) ([]*coupon.Coupon, error)
- func (s *Store) ListCoupons(ctx context.Context, appID string, opts coupon.ListOpts) ([]*coupon.Coupon, error)
- func (s *Store) ListDueSubscriptions(ctx context.Context, opts subscription.DueOpts) ([]*subscription.Subscription, error)
- func (s *Store) ListFeatures(ctx context.Context, appID string, opts feature.ListOpts) ([]*feature.Feature, error)
- func (s *Store) ListGlobalFeatures(ctx context.Context, opts feature.ListOpts) ([]*feature.Feature, error)
- func (s *Store) ListInvoices(ctx context.Context, tenantID, appID string, opts invoice.ListOpts) ([]*invoice.Invoice, error)
- func (s *Store) ListOverdueInvoices(ctx context.Context, opts invoice.OverdueOpts) ([]*invoice.Invoice, error)
- func (s *Store) ListPendingInvoices(ctx context.Context, appID string) ([]*invoice.Invoice, error)
- func (s *Store) ListPlans(ctx context.Context, appID string, opts plan.ListOpts) ([]*plan.Plan, error)
- func (s *Store) ListSubscriptions(ctx context.Context, tenantID, appID string, opts subscription.ListOpts) ([]*subscription.Subscription, error)
- func (s *Store) MarkInvoicePaid(ctx context.Context, invID id.InvoiceID, paidAt time.Time, paymentRef string) error
- func (s *Store) MarkInvoicePastDue(ctx context.Context, invID id.InvoiceID, at time.Time) (bool, error)
- func (s *Store) MarkInvoiceVoided(ctx context.Context, invID id.InvoiceID, reason string) error
- func (s *Store) Migrate(ctx context.Context) error
- func (s *Store) PauseSubscription(ctx context.Context, subID id.SubscriptionID, at time.Time) (bool, error)
- func (s *Store) Ping(ctx context.Context) error
- func (s *Store) PurgeUsage(ctx context.Context, before time.Time) (int64, error)
- func (s *Store) QueryUsage(ctx context.Context, tenantID, appID string, opts meter.QueryOpts) ([]*meter.UsageEvent, error)
- func (s *Store) RedeemCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
- func (s *Store) ResumeSubscription(ctx context.Context, subID id.SubscriptionID, r subscription.Resume) (bool, error)
- func (s *Store) SetCached(ctx context.Context, tenantID, appID, featureKey string, ...) error
- func (s *Store) SetInvoiceProvider(ctx context.Context, invID id.InvoiceID, providerID, providerName string) error
- func (s *Store) SetSubscriptionProvider(ctx context.Context, subID id.SubscriptionID, providerID, providerName string) error
- func (s *Store) UpdateCoupon(ctx context.Context, c *coupon.Coupon) error
- func (s *Store) UpdateFeature(ctx context.Context, f *feature.Feature) error
- func (s *Store) UpdateInvoice(ctx context.Context, inv *invoice.Invoice) error
- func (s *Store) UpdatePlan(ctx context.Context, p *plan.Plan) error
- func (s *Store) UpdateSubscription(ctx context.Context, sub *subscription.Subscription) error
Constants ¶
This section is empty.
Variables ¶
var Migrations = migrate.NewGroup("ledger")
Migrations is the grove migration group for the Ledger store (SQLite).
Functions ¶
This section is empty.
Types ¶
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store implements store.Store using SQLite via Grove ORM.
func (*Store) AdvanceSubscriptionPeriod ¶
func (*Store) AggregateMulti ¶
func (*Store) ApplyCoupon ¶
func (s *Store) ApplyCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
ApplyCoupon is a low-level operation kept for callers and tests that need it separately. Engine code redeems through RedeemCoupon, which records the application and increments the redemption count as one unit.
func (*Store) ArchiveFeature ¶
func (*Store) ArchivePlan ¶
func (*Store) CancelSubscription ¶
func (*Store) ChangeSubscriptionPlan ¶
func (*Store) CreateCoupon ¶
func (*Store) CreateFeature ¶
func (*Store) CreateInvoice ¶
func (*Store) CreateSubscription ¶
func (s *Store) CreateSubscription(ctx context.Context, sub *subscription.Subscription) error
func (*Store) DeleteCoupon ¶
func (*Store) DeleteFeature ¶
func (*Store) EnactSubscriptionCancel ¶
func (*Store) EndSubscriptionTrial ¶
func (*Store) GetActiveSubscription ¶
func (s *Store) GetActiveSubscription(ctx context.Context, tenantID, appID string) (*subscription.Subscription, error)
func (*Store) GetCouponByID ¶
func (*Store) GetFeature ¶
func (*Store) GetFeatureByKey ¶
func (*Store) GetInvoice ¶
func (*Store) GetInvoiceByPeriod ¶
func (*Store) GetPlanBySlug ¶
func (*Store) GetSubscription ¶
func (s *Store) GetSubscription(ctx context.Context, subID id.SubscriptionID) (*subscription.Subscription, error)
func (*Store) IncrementCouponRedemptions ¶
IncrementCouponRedemptions is a low-level operation kept for callers and tests that need it separately. Engine code redeems through RedeemCoupon, which increments the count and records the application as one unit.
func (*Store) IngestBatch ¶
func (*Store) Invalidate ¶
func (*Store) InvalidateFeature ¶
func (*Store) ListAppliedCoupons ¶
func (*Store) ListCoupons ¶
func (*Store) ListDueSubscriptions ¶
func (s *Store) ListDueSubscriptions(ctx context.Context, opts subscription.DueOpts) ([]*subscription.Subscription, error)
func (*Store) ListFeatures ¶
func (*Store) ListGlobalFeatures ¶
func (*Store) ListInvoices ¶
func (*Store) ListOverdueInvoices ¶
func (*Store) ListPendingInvoices ¶
func (*Store) ListSubscriptions ¶
func (s *Store) ListSubscriptions(ctx context.Context, tenantID, appID string, opts subscription.ListOpts) ([]*subscription.Subscription, error)
func (*Store) MarkInvoicePaid ¶
func (*Store) MarkInvoicePastDue ¶
func (*Store) MarkInvoiceVoided ¶
func (*Store) Migrate ¶
Migrate creates the required tables and indexes using the grove orchestrator.
func (*Store) PauseSubscription ¶
func (*Store) PurgeUsage ¶
func (*Store) QueryUsage ¶
func (*Store) RedeemCoupon ¶
func (s *Store) RedeemCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
RedeemCoupon records a coupon's application to a subscription and increments its redemption count in one transaction, committing only when both writes land. The cap (MaxRedemptions) is enforced by a conditional UPDATE - `times_redeemed = times_redeemed + 1 WHERE id = ? AND (max_redemptions <= 0 OR times_redeemed < max_redemptions)` - rather than a read of TimesRedeemed followed by a separate write, so two concurrent transactions racing the same coupon toward its cap cannot both read the count before either writes it: the second transaction's UPDATE blocks until the first commits or rolls back, and its WHERE then re-evaluates against whatever count the first one left behind. SQLite has no row-level locking - a writing transaction holds a database-wide RESERVED/EXCLUSIVE lock, not a lock on this one row - but the effect on this method is the same: the second UPDATE waits rather than racing the first's read.
The INSERT of the application row is deliberately the FIRST statement in the transaction, not a SELECT. A transaction that opens with a read starts deferred and only holds a SHARED lock; if it later needs to write, SQLite must upgrade that SHARED lock to RESERVED, and two transactions racing the same coupon can both be sitting on SHARED when they attempt that upgrade at the same time. SQLite treats that as an unresolvable mutual wait and fails it immediately with SQLITE_BUSY rather than queuing it behind busy_timeout - regardless of how long busy_timeout is set to, and regardless of DSN flags. Opening with a write instead means the RESERVED lock is acquired up front, with no later upgrade to fail. This is why the coupon's existence is no longer confirmed with a SELECT before the INSERT: that SELECT was the read this method used to open with.
Unknown coupon and exhausted cap now share one signal - the conditional UPDATE affects zero rows - and are told apart only after the fact: zero rows means either the coupon was never there or its cap was reached, and an existence check run at that point (with the write lock already held by the INSERT above, so it cannot itself trigger a lock-upgrade BUSY) decides which.
func (*Store) ResumeSubscription ¶
func (s *Store) ResumeSubscription(ctx context.Context, subID id.SubscriptionID, r subscription.Resume) (bool, error)
func (*Store) SetInvoiceProvider ¶
func (*Store) SetSubscriptionProvider ¶
func (*Store) UpdateCoupon ¶
func (*Store) UpdateFeature ¶
func (*Store) UpdateInvoice ¶
func (*Store) UpdateSubscription ¶
func (s *Store) UpdateSubscription(ctx context.Context, sub *subscription.Subscription) error