ETS-backed storage for Price resources.
Uses the shared store pattern via use PaperTiger.Store which provides:
- GenServer wraps ETS table
- Reads go directly to ETS (concurrent, fast)
- Writes go through GenServer (serialized, safe)
Architecture
- ETS Table:
:paper_tiger_prices(public, read_concurrency: true) - GenServer: Serializes writes, handles initialization
- Shared Implementation: All CRUD operations via PaperTiger.Store
Examples
# Direct read (no GenServer bottleneck)
{:ok, price} = PaperTiger.Store.Prices.get("price_123")
# Serialized write
price = %{id: "price_123", product: "prod_123", active: true, ...}
{:ok, price} = PaperTiger.Store.Prices.insert(price)
# Query helpers (direct ETS access)
prices = PaperTiger.Store.Prices.find_by(:product, "prod_123")
active_prices = PaperTiger.Store.Prices.find_by(:active, true)
Summary
Functions
Returns a specification to start this module under a supervisor.
Clears all prices from the store (all namespaces).
Clears all prices for a specific namespace.
Counts total prices in current namespace.
Deletes a price from the store.
Finds all prices in the current namespace whose field equals value.
Retrieves a price by ID.
Fetches a price by ID, or builds a minimal placeholder when the ID is unknown.
Fetches a price by ID, accepting a legacy plan ID in its place.
Retrieves a price only when it belongs to the given owner.
Inserts a price into the store.
Lists all prices with optional pagination.
Returns all items in a specific namespace.
Validates and applies child-resource mutations in one serialized store call.
Returns the ID prefix for this resource.
Starts the price store GenServer.
Returns the ETS table name for this store.
Updates a price in the store.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec clear() :: :ok
Clears all prices from the store (all namespaces).
Serialized write - goes through GenServer.
Useful for test cleanup. Note: This clears ALL data, not just
the current namespace. For namespace-specific cleanup, use
clear_namespace/1.
Clears all prices for a specific namespace.
Used by PaperTiger.Test to clean up after each test.
@spec count() :: non_neg_integer()
Counts total prices in current namespace.
Direct ETS access - does not go through GenServer.
@spec delete(String.t()) :: :ok
Deletes a price from the store.
Serialized write - goes through GenServer. Data is scoped to the current test namespace.
Finds all prices in the current namespace whose field equals value.
Direct ETS access - does not go through GenServer.
Returns an empty list when value is nil, since a nil reference never
identifies a parent resource.
Examples
find_by(:customer, "cus_123")
find_by(:status, "active")
Retrieves a price by ID.
Direct ETS access - does not go through GenServer. Data is scoped to the current test namespace.
Fetches a price by ID, or builds a minimal placeholder when the ID is unknown.
Callers that embed a price inside another resource (subscription items,
checkout line items) commonly receive ad-hoc price IDs that were never
created. They get a well-formed price object either way. A price map passes
through unchanged; anything else returns nil.
Direct ETS access - does not go through GenServer.
Fetches a price by ID, accepting a legacy plan ID in its place.
Stripe accepts plan IDs wherever a price ID is expected. A plan comes back
converted to price shape, with its amount as unit_amount and its interval
under recurring.
Direct ETS access - does not go through GenServer.
Retrieves a price only when it belongs to the given owner.
Missing resources and resources owned by a different parent both return
{:error, :not_found} so nested endpoints do not disclose foreign IDs.
Inserts a price into the store.
Serialized write - goes through GenServer to prevent race conditions. Data is scoped to the current test namespace.
@spec list(keyword() | map()) :: PaperTiger.List.t()
Lists all prices with optional pagination.
Direct ETS access - does not go through GenServer. Data is scoped to the current test namespace.
Options
:limit- Number of items (default: 10, max: 100):starting_after- Cursor for pagination:ending_before- Reverse cursor
Returns all items in a specific namespace.
Useful for debugging test isolation.
@spec mutate_owned(atom(), term(), [ {:insert | :update, map()} | {:delete, String.t()} ]) :: :ok | {:error, {:already_exists | :duplicate_operation | :not_found | :not_owned, String.t()}}
Validates and applies child-resource mutations in one serialized store call.
Every operation is checked before any write occurs. Inserts must use new
IDs, updates and deletes must target existing resources owned by
owner_id, and an ID may appear only once in the batch.
@spec prefix() :: String.t() | nil
Returns the ID prefix for this resource.
@spec start_link(keyword()) :: GenServer.on_start()
Starts the price store GenServer.
@spec table_name() :: atom()
Returns the ETS table name for this store.
Updates a price in the store.
Serialized write - goes through GenServer. Data is scoped to the current test namespace.