### Install AshRateLimiter with Igniter Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Use Igniter for the recommended installation method. This command adds the dependency and configures the formatter. ```bash mix igniter.install ash_rate_limiter ``` -------------------------------- ### Manual Installation of AshRateLimiter Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Add ash_rate_limiter to your mix.exs dependencies and configure the formatter. ```elixir def deps do [ {:ash_rate_limiter, "~> 1.0.0"} ] end ``` ```elixir [ import_deps: [:ash, :ash_rate_limiter], # ... other formatter config ] ``` -------------------------------- ### Setup Hammer Backend for AshRateLimiter Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Create a Hammer module, add it to the supervision tree, and configure AshRateLimiter to use it. ```elixir # lib/my_app/hammer.ex defmodule MyApp.Hammer do use Hammer, backend: :ets end # lib/my_app/application.ex def start(_type, _args) do children = [ {MyApp.Hammer, clean_period: :timer.minutes(1)} ] Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor) end # config/config.exs config :my_app, :ash_rate_limiter, hammer: MyApp.Hammer ``` -------------------------------- ### Install AshRateLimiter and Hammer Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Add the ash_rate_limiter and hammer dependencies to your mix.exs file and configure the formatter. ```elixir # mix.exs def deps do [ {:ash_rate_limiter, "~> 1.0.0"}, {:hammer, "~> 7.0"} # default backend (optional but recommended) ] end # .formatter.exs [ import_deps: [:ash, :ash_rate_limiter] ] ``` -------------------------------- ### Simple Rate Limiting Example Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Apply a simple rate limit to the create action, allowing 5 requests per minute. ```elixir rate_limit do backend MyApp.Hammer action :create, limit: 5, per: :timer.minutes(1) end ``` -------------------------------- ### mix igniter.install ash_rate_limiter Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Scaffolds a complete AshRateLimiter setup using Igniter. This command adds the `:hammer` dependency, creates a `MyApp.Hammer` module with an ETS backend, adds it to the supervision tree, imports the DSL formatter, and configures Spark DSL section ordering. ```APIDOC ### `mix igniter.install ash_rate_limiter` Scaffolds a complete AshRateLimiter setup using [Igniter](https://hexdocs.pm/igniter): adds the `:hammer` dependency, creates a `MyApp.Hammer` module using the ETS backend, adds it to the supervision tree, imports the DSL formatter, and configures Spark DSL section ordering. ```bash mix igniter.install ash_rate_limiter ``` ``` -------------------------------- ### Manual Integration with AshRateLimiter Preparation Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Manually add rate limiting to a specific action using `AshRateLimiter.Preparation`. This example limits the read action. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp actions do # ... read :read do prepare {AshRateLimiter.Preparation, limit: 100, per: :timer.minutes(1)} end end end ``` -------------------------------- ### Configure Rate Limiting for Actions Source: https://github.com/ash-project/ash_rate_limiter/blob/main/documentation/dsls/DSL-AshRateLimiter.md This example shows how to configure rate limiting for actions using the `rate_limit` DSL. It specifies the backend module and configures a rate limit for the `:create` action, allowing 10 events per 5 minutes. ```elixir rate_limit do backend MyApp.RateLimiter action :create, limit: 10, per: :timer.minutes(5) end ``` -------------------------------- ### Manual Integration with AshRateLimiter Change Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Manually add rate limiting to a specific action using `AshRateLimiter.Change`. This example limits the create action. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp actions do create :create do change {AshRateLimiter.Change, limit: 10, per: :timer.minutes(5)} end # ... end end ``` -------------------------------- ### Implement Resource with AshRateLimiter Extension Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Defines an Ash Resource that includes the AshRateLimiter extension and configures rate limits for various actions. This example demonstrates global, per-user, and per-record rate limiting. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp, data_layer: AshPostgres.DataLayer, extensions: [AshRateLimiter] rate_limit do backend MyApp.Hammer # Global create limit (all users share the bucket by default) action :create, limit: 100, per: :timer.minutes(1) # Per-user read limit using custom key action :read, limit: 500, per: :timer.minutes(1), key: fn _query, ctx -> case ctx[:actor] do %{id: id} -> "user:#{id}:post:read" nil -> "anonymous:post:read" end end # Per-user-per-record update limit, checked before the transaction action :update, limit: 30, per: :timer.minutes(5), on: :before_transaction, key: fn changeset, ctx -> "user:#{ctx.actor.id}:post:#{changeset.data.id}:update" end end postgres do table "posts" repo MyApp.Repo end actions do defaults [:read, :destroy, create: :*, update: :*] end attributes do uuid_primary_key :id attribute :title, :string, allow_nil?: false, public?: true attribute :body, :string, allow_nil?: false, public?: true timestamps() end end ``` -------------------------------- ### Define a Hammer Module Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Create a Hammer module to handle rate limiting. This example uses the :ets backend. ```elixir defmodule MyApp.Hammer do use Hammer, backend: :ets end ``` -------------------------------- ### Configure Rate Limiting in Ash Resource Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Use the `rate_limit` DSL section in your Ash resource to define limits for actions. This example sets limits for create and read actions. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp, extensions: [AshRateLimiter] rate_limit do # Configure hammer backend backend MyApp.Hammer # Limit create action to 10 requests per 5 minutes action :create, limit: 10, per: :timer.minutes(5) # Limit read action to 100 requests per minute action :read, limit: 100, per: :timer.minutes(1) end # ... rest of your resource definition end ``` -------------------------------- ### Handle Rate Limit Exceeded Error Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Demonstrates how to handle a rate limit exceeded error when performing an Ash action. This example shows how to catch the `AshRateLimiter.LimitExceeded` error and provide a user-friendly message. ```elixir case Ash.create( MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Hello", body: "World"}), actor: current_user ) do {:ok, post} -> IO.inspect(post) {:error, %Ash.Error.Forbidden{errors: [%AshRateLimiter.LimitExceeded{}]}} -> IO.puts("Rate limit exceeded — try again later") end ``` -------------------------------- ### Generate Key for Action with AshRateLimiter Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Demonstrates various ways to generate a rate limiting key using `AshRateLimiter.key_for_action/3`. It shows basic usage, including primary keys for update/destroy, and options to include actor attributes and tenant information. ```elixir MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Hello"}) |> AshRateLimiter.key_for_action(%{}) # => "my_app/post/create" ``` ```elixir post |> Ash.Changeset.for_update(:update, %{title: "Updated"}) |> AshRateLimiter.key_for_action(%{}) # => "my_app/post/update?id=" ``` ```elixir post |> Ash.Changeset.for_destroy(:destroy) |> AshRateLimiter.key_for_action(%{}, include_pk?: false) # => "my_app/post/destroy" ``` ```elixir MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Hello"}) |> AshRateLimiter.key_for_action( %{actor: %{id: "u1", role: :admin}}, include_actor_attributes: [:role] ) # => "my_app/post/create?actor%5Brole%5D=admin" ``` ```elixir MyApp.Post |> Ash.Changeset.new() |> Ash.Changeset.set_tenant("acme-corp") |> Ash.Changeset.for_create(:create, %{title: "Hello"}) |> AshRateLimiter.key_for_action(%{}, include_tenant?: true) # => "my_app/post/create?tenant=acme-corp" ``` -------------------------------- ### Configure Hammer Backend for AshRateLimiter Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Configure AshRateLimiter to use your specified Hammer module. ```elixir config :my_app, :ash_rate_limiter, hammer: MyApp.Hammer ``` -------------------------------- ### Custom Rate Limiter Backend Implementation Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Implement the `AshRateLimiter.Backend` behaviour to create custom rate limiting backends. The `hit/3` callback determines whether to allow or deny requests based on the limit. ```elixir # Custom ETS-based counting backend (simplified) defmodule MyApp.RateLimiter do @behaviour AshRateLimiter.Backend @table __MODULE__ def child_spec(_opts) do %{id: __MODULE__, start: {__MODULE__, :start_link, []}} end def start_link do :ets.new(@table, [:public, :set, :named_table, read_concurrency: true, write_concurrency: true]) :ignore end @impl true def hit(key, _window_ms, limit) do count = :ets.update_counter(@table, key, {2, 1}, {key, 0}) if count <= limit, do: {:allow, count}, else: {:deny, count} end end ``` ```elixir # Using Hammer as backend (recommended for production) defmodule MyApp.Hammer do use Hammer, backend: :ets # Hammer already implements AshRateLimiter.Backend via the use macro end # Wire it up in a resource defmodule MyApp.Post do use Ash.Resource, domain: MyApp, extensions: [AshRateLimiter] rate_limit do backend MyApp.Hammer action :create, limit: 10, per: :timer.minutes(1) end end ``` -------------------------------- ### Configure Key Function with Options Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Use the built-in key function with options to specify actor attributes to include. ```elixir key: {&AshRateLimiter.key_for_action/2, include_actor_attributes: [:role]} ``` -------------------------------- ### Inline Rate Limiting Preparations Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Use `AshRateLimiter.BuiltinPreparations.rate_limit/1` to inline rate limiting directly within `read` or generic action definitions. Configure limits, durations, and custom keys. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp actions do read :read do # 100 reads per minute using default key prepare AshRateLimiter.BuiltinPreparations.rate_limit( limit: 100, per: :timer.minutes(1) ) end read :search do argument :query, :string, allow_nil?: false # Per-user rate limit on expensive search prepare AshRateLimiter.BuiltinPreparations.rate_limit( limit: 10, per: :timer.minutes(1), key: fn _query, ctx -> "user:#{ctx.actor.id}:post:search" end ) end action :export, :string do prepare AshRateLimiter.BuiltinPreparations.rate_limit( limit: 3, per: :timer.hours(1), on: :before_transaction ) run fn _input, _ctx -> {:ok, "..."} end end end end ``` -------------------------------- ### Configure Rate Limiting for a Single Action Source: https://github.com/ash-project/ash_rate_limiter/blob/main/documentation/dsls/DSL-AshRateLimiter.md This snippet demonstrates configuring rate limiting for a specific action. It uses the `action` DSL within `rate_limit` to define limits, periods, and optional keys or descriptions for the rate limiting mechanism. ```elixir action :create, limit: 10, per: :timer.minutes(5) ``` -------------------------------- ### rate_limit.action Configuration Source: https://github.com/ash-project/ash_rate_limiter/blob/main/documentation/dsls/DSL-AshRateLimiter.md Configure rate limiting for a single action. This is done by adding a global change or preparation to the resource. ```APIDOC ## rate_limit.action Configuration ### Description Configure rate limiting for a single action. It does this by adding a global change or preparation to the resource with the provided configuration. For more advanced configuration you can add the change/preparation directly to your action using `AshRateLimiter.BuiltinChanges.rate_limit/1` or `AshRateLimiter.BuiltinPreparations.rate_limit/1`. ### Arguments | Name | Type | Default | Docs | |------|------|---------|------| | [`action`](#rate_limit-action-action) | `atom` | | The name of the action to limit | ### Options | Name | Type | Default | Docs | |------|------|---------|------| | [`limit`](#rate_limit-action-limit) | `pos_integer` | | The maximum number of events allowed within the given period | | [`per`](#rate_limit-action-per) | `pos_integer` | | The time period (in milliseconds) for in which events are counted | | [`key`](#rate_limit-action-key) | `String.t | (any -> any) | (any, any -> any)` | `&AshRateLimiter.key_for_action/2` | The key used to identify the event. See above. | | [`description`](#rate_limit-action-description) | `String.t` | | A description of the rate limit | | [`on`](#rate_limit-action-on) | `:before_action | :before_transaction` | `:before_action` | The lifecycle hook to use for rate limiting. `:before_action` (default) runs inside the transaction; `:before_transaction` runs outside the transaction, after validations. | ``` -------------------------------- ### rate_limit.action arguments and options Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Defines the arguments and options available for the `rate_limit.action` configuration in Ash Rate Limiter. ```APIDOC ## `rate_limit.action` arguments and options | Name | Type | Required | Default | Description | |---------------|-------------------------------------|----------|----------------------------------|-------------| | `action` | `atom` | yes | — | Name of the action to limit | | `limit` | `pos_integer` | yes | — | Max events allowed in the window | | `per` | `pos_integer` (ms) | yes | — | Window duration in milliseconds | | `key` | `String \| fun/1 \| fun/2` | no | `&AshRateLimiter.key_for_action/2` | Bucket key or key-generating function | | `description` | `String` | no | `nil` | Human-readable label for the limit | | `on` | `:before_action \| :before_transaction` | no | `:before_action` | Lifecycle hook: inside or outside DB transaction | ``` -------------------------------- ### Configure Application Children with Hammer Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Configures the application's children to include the custom Hammer backend. This ensures the rate limiter is initialized with the specified clean period. ```elixir children = [{MyApp.Hammer, clean_period: :timer.minutes(1)}] ``` -------------------------------- ### Introspecting Rate Limiter Configuration Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Use `AshRateLimiter.Info` to retrieve runtime configuration details of rate limiting, such as the backend module and defined actions. ```elixir # Retrieve the configured backend module for a resource AshRateLimiter.Info.rate_limit_backend!(MyApp.Post) # => MyApp.Hammer # List all rate_limit action entities defined on a resource AshRateLimiter.Info.rate_limit_actions(MyApp.Post) # => [%AshRateLimiter{action: :create, limit: 10, per: 300000, ...}] ``` -------------------------------- ### Configure Rate Limits with `rate_limit` DSL Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Define rate limits for actions within an Ash.Resource using the `rate_limit` DSL section. Specify the backend, action, limit, period, and optionally a key or description. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp, extensions: [AshRateLimiter] rate_limit do # Required: module implementing AshRateLimiter.Backend backend MyApp.Hammer # Limit :create to 10 requests per 5 minutes (global key per resource+action) action :create, limit: 10, per: :timer.minutes(5) # Limit :read to 100 per minute, keyed per authenticated user action :read, limit: 100, per: :timer.minutes(1), key: fn _query, context -> "user:#{context.actor.id}:read" end # Limit :update, run check outside the DB transaction action :update, limit: 20, per: :timer.minutes(5), on: :before_transaction # Limit a generic action with a description action :export, limit: 5, per: :timer.hours(1), description: "Prevent export abuse" end actions do defaults [:read, :destroy, create: :*, update: :*] action :export, :string do run fn _input, _ctx -> {:ok, "csv data"} end end end attributes do uuid_v7_primary_key :id attribute :title, :string, allow_nil?: false, public?: true end end ``` -------------------------------- ### AshRateLimiter.BuiltinPreparations.rate_limit/1 Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Inlines a rate-limit preparation directly inside a `read` or generic action definition. It accepts options like `limit`, `per`, and `key` to configure the rate limiting behavior. ```APIDOC ## `AshRateLimiter.BuiltinPreparations.rate_limit/1` Inlines a rate-limit preparation directly inside a `read` or generic action definition. Identical options to `BuiltinChanges.rate_limit/1` but returns `{AshRateLimiter.Preparation, opts}`. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp actions do read :read do # 100 reads per minute using default key prepare AshRateLimiter.BuiltinPreparations.rate_limit( limit: 100, per: :timer.minutes(1) ) end read :search do argument :query, :string, allow_nil?: false # Per-user rate limit on expensive search prepare AshRateLimiter.BuiltinPreparations.rate_limit( limit: 10, per: :timer.minutes(1), key: fn _query, ctx -> "user:#{ctx.actor.id}:post:search" end ) end action :export, :string do prepare AshRateLimiter.BuiltinPreparations.rate_limit( limit: 3, per: :timer.hours(1), on: :before_transaction ) run fn _input, _ctx -> {:ok, "..."} end end end end ``` ``` -------------------------------- ### AshRateLimiter.Backend behaviour Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Implement this behaviour to provide a custom rate limiting backend. The `hit/3` callback must return `{:allow, count}` when under limit or `{:deny, count}` when exceeded. ```APIDOC ## `AshRateLimiter.Backend` behaviour Implement this behaviour to provide a custom rate limiting backend (e.g., Redis, a distributed counter, or a test double). The single callback `hit/3` must return `{:allow, count}` when under limit or `{:deny, count}` when exceeded. ```elixir # Custom ETS-based counting backend (simplified) defmodule MyApp.RateLimiter do @behaviour AshRateLimiter.Backend @table __MODULE__ def child_spec(_opts) do %{id: __MODULE__, start: {__MODULE__, :start_link, []}} end def start_link do :ets.new(@table, [:public, :set, :named_table, read_concurrency: true, write_concurrency: true]) :ignore end @impl true def hit(key, _window_ms, limit) do count = :ets.update_counter(@table, key, {2, 1}, {key, 0}) if count <= limit, do: {:allow, count}, else: {:deny, count} end end # Using Hammer as backend (recommended for production) defmodule MyApp.Hammer do use Hammer, backend: :ets # Hammer already implements AshRateLimiter.Backend via the use macro end # Wire it up in a resource defmodule MyApp.Post do use Ash.Resource, domain: MyApp, extensions: [AshRateLimiter] rate_limit do backend MyApp.Hammer action :create, limit: 10, per: :timer.minutes(1) end end ``` ### Callback ```elixir @callback hit(key :: String.t(), window_ms :: pos_integer(), limit :: pos_integer()) :: {:allow, pos_integer()} | {:deny, pos_integer()} ``` ``` -------------------------------- ### Inline Rate Limiting with AshRateLimiter.BuiltinChanges.rate_limit Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Shows how to define rate limits directly within Ash resource actions using `AshRateLimiter.BuiltinChanges.rate_limit/1`. This is useful for applying different limits to specific actions or for custom bucketing logic. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp actions do create :create do # 5 creates per minute, keyed per user change AshRateLimiter.BuiltinChanges.rate_limit( limit: 5, per: :timer.minutes(1), key: fn _changeset, ctx -> "user:#{ctx.actor.id}:post:create" end ) end create :admin_create do # Higher limit for admins, run before the transaction change AshRateLimiter.BuiltinChanges.rate_limit( limit: 100, per: :timer.minutes(1), on: :before_transaction ) end update :update do accept [:title, :body] change AshRateLimiter.BuiltinChanges.rate_limit( limit: 20, per: :timer.minutes(5) ) end end end ``` -------------------------------- ### Run Ash Rate Limiter Upgrade Script Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Executes the upgrade script for AshRateLimiter, handling version migration steps. This command is used to apply changes like adding dependencies or renaming DSL options. ```bash mix igniter.upgrade ash_rate_limiter 0.2.1 1.0.0 # Renames `hammer MyApp.Hammer` → `backend MyApp.Hammer` in all rate_limit blocks ``` -------------------------------- ### AshRateLimiter.key_for_action/3 Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt The default key generation function for Ash Rate Limiter. It constructs a URL-like string for bucketing rate limits. ```APIDOC ## `AshRateLimiter.key_for_action/3` The default key generation function. Builds a URL-like string from the domain short name, resource short name, and action name. For `update` and `destroy` actions it appends primary key values as query parameters. Accepts options to include actor attributes and/or tenant for finer-grained bucketing. ```elixir # Basic usage — returns "my_app/post/create" MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Hello"}) |> AshRateLimiter.key_for_action(%{}) # => "my_app/post/create" # Update includes primary key — "my_app/post/update?id=" post |> Ash.Changeset.for_update(:update, %{title: "Updated"}) |> AshRateLimiter.key_for_action(%{}) # => "my_app/post/update?id=0196ebc0-83b4-7938-bc9d-22ae65dbec0b" # Exclude PK from key post |> Ash.Changeset.for_destroy(:destroy) |> AshRateLimiter.key_for_action(%{}, include_pk?: false) # => "my_app/post/destroy" # Include actor role attribute MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Hello"}) |> AshRateLimiter.key_for_action( %{actor: %{id: "u1", role: :admin}}, include_actor_attributes: [:role] ) # => "my_app/post/create?actor%5Brole%5D=admin" # Include tenant MyApp.Post |> Ash.Changeset.new() |> Ash.Changeset.set_tenant("acme-corp") |> Ash.Changeset.for_create(:create, %{title: "Hello"}) |> AshRateLimiter.key_for_action(%{}, include_tenant?: true) # => "my_app/post/create?tenant=acme-corp" ``` ### `key_for_action/3` options | Option | Type | Default | Description | |---------------------------|-----------------|---------|-------------| | `include_pk?` | `boolean` | `true` | Append primary keys for update/destroy | | `include_actor_attributes`| `list(atom)` | `[]` | Actor map fields to include as query params | | `include_tenant?` | `boolean` | `false` | Include the changeset/query tenant | ``` -------------------------------- ### Add Hammer to Application Supervision Tree Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Integrate your Hammer module into the application's supervision tree. Configure the :clean_period for Hammer. ```elixir children = [ # ... # Add the line below: {MyApp.Hammer, clean_period: :timer.minutes(1)}, # ... ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor.start_link(children, opts) ``` -------------------------------- ### rate_limit DSL Configuration Source: https://github.com/ash-project/ash_rate_limiter/blob/main/documentation/dsls/DSL-AshRateLimiter.md Configure rate limiting for actions within an Ash resource. This DSL allows specifying the backend, and then configuring rate limits for individual actions. ```APIDOC ## rate_limit DSL ### Description Configure rate limiting for actions. ### Backend This library uses a pluggable backend for rate limiting. Any module implementing the `AshRateLimiter.Backend` behaviour can be used. ### Keys The backend uses a "key" to identify which bucket to allocate an event against. You can use this to tune the rate limit for specific users or events. You can provide either a statically configured string key, or a function of arity one or two, which when given a query/changeset and optional context object can generate a key. The default is `AshRateLimiter.key_for_action/2`. ### Options | Name | Type | Default | Docs | |------|------|---------|------| | [`backend`](#rate_limit-backend) | `module` | | The rate limiting backend module | ### Nested DSLs * [action](#rate_limit-action) ### Examples ```elixir rate_limit do backend MyApp.RateLimiter action :create, limit: 10, per: :timer.minutes(5) end ``` ``` -------------------------------- ### AshRateLimiter.Info (introspection) Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Auto-generated introspection module that exposes the compiled `rate_limit` DSL configuration at runtime via `Spark.InfoGenerator`. ```APIDOC ## `AshRateLimiter.Info` (introspection) Auto-generated introspection module that exposes the compiled `rate_limit` DSL configuration at runtime via `Spark.InfoGenerator`. ```elixir # Retrieve the configured backend module for a resource AshRateLimiter.Info.rate_limit_backend!(MyApp.Post) # => MyApp.Hammer # List all rate_limit action entities defined on a resource AshRateLimiter.Info.rate_limit_actions(MyApp.Post) # => [%AshRateLimiter{action: :create, limit: 10, per: 300000, ...}] ``` ``` -------------------------------- ### Per-User Rate Limiting Configuration Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Configure rate limiting that is specific to a user. The `key` function generates a unique key based on the actor's ID and the action. ```elixir rate_limit do backend MyApp.Hammer action :create, limit: 10, per: :timer.minutes(5), key: fn changeset, context -> "user:#{context.actor.id}:create" end end ``` -------------------------------- ### Custom Key Function for User and Action Rate Limiting Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Define a custom key function to group requests by user ID and action name for rate limiting. ```elixir key: fn changeset, context -> "user:#{context.actor.id}:action:#{changeset.action.name}" end ``` -------------------------------- ### AshRateLimiter.BuiltinChanges.rate_limit/1 Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Allows inlining a rate-limit change directly within a mutation action definition in Ash. ```APIDOC ## `AshRateLimiter.BuiltinChanges.rate_limit/1` Inlines a rate-limit change directly inside a mutation action definition, bypassing the global `rate_limit` DSL block. Useful when different `create` or `update` actions on the same resource need different limits. ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp actions do create :create do # 5 creates per minute, keyed per user change AshRateLimiter.BuiltinChanges.rate_limit( limit: 5, per: :timer.minutes(1), key: fn _changeset, ctx -> "user:#{ctx.actor.id}:post:create" end ) end create :admin_create do # Higher limit for admins, run before the transaction change AshRateLimiter.BuiltinChanges.rate_limit( limit: 100, per: :timer.minutes(1), on: :before_transaction ) end update :update do accept [:title, :body] change AshRateLimiter.BuiltinChanges.rate_limit( limit: 20, per: :timer.minutes(5) ) end end end ``` ### Options | Option | Type | Required | Default | Description | |---------|-------------------------------------|----------|----------------------------------|-------------| | `limit` | `pos_integer` | yes | — | Max events in window | | `per` | `pos_integer` (ms) | yes | — | Window in milliseconds | | `key` | `String \| fun/1 \| fun/2` | no | `&AshRateLimiter.key_for_action/2` | Bucket key | | `on` | `:before_action \| :before_transaction` | no | `:before_action` | Lifecycle hook | ``` -------------------------------- ### Custom Key Function for IP Address Rate Limiting Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Define a custom key function to group requests by IP address for rate limiting. ```elixir key: fn _changeset, context -> "ip:#{context[:ip_address]}" end ``` -------------------------------- ### Rate Limiting Multiple Actions Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Define different rate limits for multiple actions within the same resource. ```elixir rate_limit do backend MyApp.Hammer action :create, limit: 10, per: :timer.minutes(5) action :update, limit: 20, per: :timer.minutes(5) action :read, limit: 100, per: :timer.minutes(1) end ``` -------------------------------- ### AshRateLimiter.LimitExceeded Error Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt The error raised when an action invocation exceeds its configured rate limit. It is a `:forbidden`-class Splode error and, when `plug` is present, implements `Plug.Exception` returning HTTP status `429`. ```APIDOC ## `AshRateLimiter.LimitExceeded` The error raised when an action invocation exceeds its configured rate limit. It is a `:forbidden`-class Splode error and, when `plug` is present, implements `Plug.Exception` returning HTTP status `429`. ```elixir # Calling a rate-limited action result = Ash.create( MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Hello", body: "World"}), actor: current_user ) case result do {:ok, post} -> {:ok, post} {:error, %Ash.Error.Forbidden{errors: [%AshRateLimiter.LimitExceeded{} = err]}} -> # err fields: :action, :backend, :limit, :per, :key Logger.warning("Rate limit hit: action=#{err.action} limit=#{err.limit}/#{err.per}ms") {:error, :rate_limited} {:error, other} -> {:error, other} end # In a Phoenix controller the error automatically becomes a 429 response # when using Ash.Phoenix error helpers or a custom fallback controller: defmodule MyAppWeb.FallbackController do use Phoenix.Controller def call(conn, {:error, %Ash.Error.Forbidden{errors: [%AshRateLimiter.LimitExceeded{}]}}) do conn |> put_status(:too_many_requests) |> json(%{error: "Too many requests. Please slow down."}) end end ``` ### Error fields | Field | Description | |-----------|-------------| | `action` | The action atom that was rate-limited | | `backend` | The backend module that denied the request | | `limit` | The configured max events | | `per` | The window duration in milliseconds | | `key` | The bucket key that was exhausted | ``` -------------------------------- ### Handle Rate Limit Exceeded Error Source: https://github.com/ash-project/ash_rate_limiter/blob/main/README.md Catch the AshRateLimiter.LimitExceeded exception to handle rate limit errors. In web applications, this exception has Plug.Exception behavior for automatic HTTP 429 responses. ```elixir case MyApp.create_post(attrs) do {:ok, post} -> # Success {:ok, post} {:error, %AshRateLimiter.LimitExceeded{} = error} -> # Rate limit exceeded {:error, "Too many requests, please try again later"} {:error, other_error} -> # Handle other errors {:error, other_error} end ``` -------------------------------- ### Handling Rate Limit Exceeded Errors Source: https://context7.com/ash-project/ash_rate_limiter/llms.txt Catch `AshRateLimiter.LimitExceeded` errors, which are `:forbidden`-class Splode errors. In Phoenix, this can automatically result in a 429 Too Many Requests response. ```elixir # Calling a rate-limited action result = Ash.create( MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Hello", body: "World"}), actor: current_user ) case result do {:ok, post} -> {:ok, post} {:error, %Ash.Error.Forbidden{errors: [%AshRateLimiter.LimitExceeded{} = err]}} -> # err fields: :action, :backend, :limit, :per, :key Logger.warning("Rate limit hit: action=#{err.action} limit=#{err.limit}/#{err.per}ms") {:error, :rate_limited} {:error, other} -> {:error, other} end ``` ```elixir # In a Phoenix controller the error automatically becomes a 429 response # when using Ash.Phoenix error helpers or a custom fallback controller: defmodule MyAppWeb.FallbackController do use Phoenix.Controller def call(conn, {:error, %Ash.Error.Forbidden{errors: [%AshRateLimiter.LimitExceeded{}]}}) do conn |> put_status(:too_many_requests) |> json(%{error: "Too many requests. Please slow down."}) end end ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.