diff --git a/config/config.exs b/config/config.exs index ff0e34f4..e56a53fd 100644 --- a/config/config.exs +++ b/config/config.exs @@ -2,7 +2,6 @@ import Config config :ash, flags: [ - read_uses_flow?: System.get_env("FLAG_READ_USES_FLOW", "false") == "true", ash_three?: System.get_env("FLAG_ASH_THREE", "false") == "true" ] diff --git a/documentation/dsls/DSL:-Ash.Api.md b/documentation/dsls/DSL:-Ash.Api.md index d36f4fde..9d9700c6 100644 --- a/documentation/dsls/DSL:-Ash.Api.md +++ b/documentation/dsls/DSL:-Ash.Api.md @@ -3,13 +3,6 @@ This file was generated by Spark. Do not edit it by hand. --> # DSL: Ash.Api.Dsl -Apis are the entrypoints for working with your resources. - -Apis may optionally include a list of resources, in which case they can be -used as an `Ash.Registry` in various places. This is for backwards compatibility, -but if at all possible you should define an `Ash.Registry` if you are using an extension -that requires a list of resources. For example, most extensions look for two application -environment variables called `:ash_apis` and `:ash_registries` to find any potential registries ## api diff --git a/documentation/dsls/DSL:-Ash.Flow.md b/documentation/dsls/DSL:-Ash.Flow.md index d116ca8a..0d32ea46 100644 --- a/documentation/dsls/DSL:-Ash.Flow.md +++ b/documentation/dsls/DSL:-Ash.Flow.md @@ -3,12 +3,6 @@ This file was generated by Spark. Do not edit it by hand. --> # DSL: Ash.Flow.Dsl -The built in flow DSL. - -## Halting - -Steps can be halted, which will stop the flow from continuing and return a halted flow. To attach a specific reason, use a `halt_reason`. -If you need more complex halting logic, then you'd want to use a custom step, and return `{:error, Ash.Error.Flow.Halted.exception(...)}` ## flow diff --git a/documentation/topics/glossary.md b/documentation/topics/glossary.md index 249409be..1a1982c8 100644 --- a/documentation/topics/glossary.md +++ b/documentation/topics/glossary.md @@ -33,7 +33,7 @@ See the [Aggregates guide](/documentation/topics/aggregates.md) for more informa A method of broadly separating resources into different [bounded contexts](https://martinfowler.com/bliki/BoundedContext.html). Small apps might only have one API, in which case you can set-and-forget it, but apps with larger domains can benefit from different contexts having different views of the same resource. -See `Ash.Api.Dsl` for more information. +See `d:Ash.Api` for more information. ## Attribute diff --git a/lib/ash.ex b/lib/ash.ex index d8d87548..2ac3b770 100644 --- a/lib/ash.ex +++ b/lib/ash.ex @@ -4,14 +4,111 @@ defmodule Ash do """ for {function, arity} <- Ash.Api.Functions.functions() do + if function == :load do + def load({:ok, result}, load) do + load(result, load) + end + + def load({:error, error}, _), do: {:error, error} + + def load([], _), do: {:ok, []} + def load(nil, _), do: {:ok, nil} + + def load(%page_struct{results: []} = page, _) + when page_struct in [Ash.Page.Keyset, Ash.Page.Offset] do + {:ok, page} + end + end + + if function == :load! do + def load!({:ok, result}, load) do + {:ok, load!(result, load)} + end + + def load!({:error, error}, _), do: raise(Ash.Error.to_error_class(error)) + def load!([], _), do: [] + def load!(nil, _), do: nil + + def load!(%page_struct{results: []} = page, _) + when page_struct in [Ash.Page.Keyset, Ash.Page.Offset] do + page + end + end + args = Macro.generate_arguments(arity, __MODULE__) - defdelegate unquote(function)(unquote_splicing(args)), to: Ash.Api.GlobalInterface + docs_arity = + if function in Ash.Api.Functions.no_opts_functions() do + arity + else + arity + 1 + end + + @doc "Calls `c:Ash.Api.#{function}/#{docs_arity}` on the resource's configured api. See those callback docs for more." + def unquote(function)(unquote_splicing(args)) do + resource = + Ash.Api.GlobalInterface.resource_from_args!(unquote(function), unquote(arity), [ + unquote_splicing(args) + ]) + + api = Ash.Resource.Info.api(resource) + + if !api do + Ash.Api.GlobalInterface.raise_no_api_error!(resource, unquote(function), unquote(arity)) + end + + apply(api, unquote(function), [unquote_splicing(args)]) + end unless function in Ash.Api.Functions.no_opts_functions() do args = Macro.generate_arguments(arity + 1, __MODULE__) - defdelegate unquote(function)(unquote_splicing(args)), to: Ash.Api.GlobalInterface + if function == :load! do + def load!({:ok, result}, load, opts) do + {:ok, load(result, load, opts)} + end + + def load!({:error, error}, _, _), do: raise(Ash.Error.to_error_class(error)) + + def load!(nil, _, _), do: nil + def load!([], _, _), do: [] + + def load!(%page_struct{results: []} = page, _, _) + when page_struct in [Ash.Page.Keyset, Ash.Page.Offset] do + page + end + end + + if function == :load do + def load({:ok, result}, load, opts) do + load(result, load, opts) + end + + def load({:error, error}, _, _), do: {:error, error} + def load([], _, _), do: {:ok, []} + def load(nil, _, _), do: {:ok, nil} + + def load(%page_struct{results: []} = page, _, _) + when page_struct in [Ash.Page.Keyset, Ash.Page.Offset] do + {:ok, page} + end + end + + @doc "Calls `c:Ash.Api.#{function}/#{arity + 1}` on the resource's configured api. See those callback docs for more." + def unquote(function)(unquote_splicing(args)) do + resource = + Ash.Api.GlobalInterface.resource_from_args!(unquote(function), unquote(arity), [ + unquote_splicing(args) + ]) + + api = Ash.Resource.Info.api(resource) + + if !api do + Ash.Api.GlobalInterface.raise_no_api_error!(resource, unquote(function), unquote(arity)) + end + + apply(api, unquote(function), [unquote_splicing(args)]) + end end end @@ -32,87 +129,14 @@ defmodule Ash do end end - @doc """ - Gets all of the ash context so it can be set into a new process. + @doc deprecated: "See `Ash.ProcessHelpers`. This alias will be removed in 3.0" + defdelegate get_context_for_transfer(opts \\ []), to: Ash.ProcessHelpers + @doc deprecated: "See `Ash.ProcessHelpers`. This alias will be removed in 3.0" + defdelegate transfer_context(term, opts \\ []), to: Ash.ProcessHelpers - Use `transfer_context/1` in the new process to set the context. - """ - @spec get_context_for_transfer(opts :: Keyword.t()) :: term - def get_context_for_transfer(opts \\ []) do - context = Ash.get_context() - actor = Process.get(:ash_actor) - authorize? = Process.get(:ash_authorize?) - tenant = Process.get(:ash_tenant) - tracer = Process.get(:ash_tracer) - - tracer_context = - opts[:tracer] - |> List.wrap() - |> Enum.concat(List.wrap(tracer)) - |> Map.new(fn tracer -> - {tracer, Ash.Tracer.get_span_context(tracer)} - end) - - %{ - context: context, - actor: actor, - tenant: tenant, - authorize?: authorize?, - tracer: tracer, - tracer_context: tracer_context - } - end - - @spec transfer_context(term, opts :: Keyword.t()) :: :ok - def transfer_context( - %{ - context: context, - actor: actor, - tenant: tenant, - authorize?: authorize?, - tracer: tracer, - tracer_context: tracer_context - }, - _opts \\ [] - ) do - case actor do - {:actor, actor} -> - Ash.set_actor(actor) - - _ -> - :ok - end - - case tenant do - {:tenant, tenant} -> - Ash.set_tenant(tenant) - - _ -> - :ok - end - - case authorize? do - {:authorize?, authorize?} -> - Ash.set_authorize?(authorize?) - - _ -> - :ok - end - - Ash.set_tracer(tracer) - - Enum.each(tracer_context || %{}, fn {tracer, tracer_context} -> - Ash.Tracer.set_span_context(tracer, tracer_context) - end) - - Ash.set_context(context) - end - - @doc """ - Sets context into the process dictionary that is used for all changesets and queries. - - In Ash 3.0, this will be updated to deep merge - """ + @doc deprecated: """ + Sets context into the process dictionary that is used for all changesets and queries. + """ @spec set_context(map) :: :ok def set_context(map) do Process.put(:ash_context, map) @@ -120,9 +144,9 @@ defmodule Ash do :ok end - @doc """ - Deep merges context into the process dictionary that is used for all changesets and queries. - """ + @doc deprecated: """ + Deep merges context into the process dictionary that is used for all changesets and queries. + """ @spec merge_context(map) :: :ok def merge_context(map) do update_context(&Ash.Helpers.deep_merge_maps(&1, map)) @@ -130,9 +154,9 @@ defmodule Ash do :ok end - @doc """ - Updates the context into the process dictionary that is used for all changesets and queries. - """ + @doc deprecated: """ + Updates the context into the process dictionary that is used for all changesets and queries. + """ @spec update_context((map -> map)) :: :ok def update_context(fun) do context = Process.get(:ash_context, %{}) @@ -141,9 +165,9 @@ defmodule Ash do :ok end - @doc """ - Sets actor into the process dictionary that is used for all changesets and queries. - """ + @doc deprecated: """ + Sets actor into the process dictionary that is used for all changesets and queries. + """ @spec set_actor(map) :: :ok def set_actor(map) do Process.put(:ash_actor, {:actor, map}) @@ -151,9 +175,9 @@ defmodule Ash do :ok end - @doc """ - Sets authorize? into the process dictionary that is used for all changesets and queries. - """ + @doc deprecated: """ + Sets authorize? into the process dictionary that is used for all changesets and queries. + """ @spec set_authorize?(map) :: :ok def set_authorize?(map) do Process.put(:ash_authorize?, {:authorize?, map}) @@ -161,9 +185,9 @@ defmodule Ash do :ok end - @doc """ - Sets the tracer into the process dictionary that will be used to trace requests - """ + @doc deprecated: """ + Sets the tracer into the process dictionary that will be used to trace requests + """ @spec set_tracer(module | list(module)) :: :ok def set_tracer(module) do case Process.get(:ash_tracer, module) do @@ -174,9 +198,9 @@ defmodule Ash do :ok end - @doc """ - Removes a tracer from the process dictionary. - """ + @doc deprecated: """ + Removes a tracer from the process dictionary. + """ @spec remove_tracer(module | list(module)) :: :ok def remove_tracer(module) do case Process.get(:ash_tracer, module) do @@ -187,9 +211,9 @@ defmodule Ash do :ok end - @doc """ - Gets the current actor from the process dictionary - """ + @doc deprecated: """ + Gets the current actor from the process dictionary + """ @spec get_actor() :: term() def get_actor do case Process.get(:ash_actor) do @@ -201,9 +225,9 @@ defmodule Ash do end end - @doc """ - Gets the current tracer - """ + @doc deprecated: """ + Gets the current tracer + """ @spec get_tracer() :: term() def get_tracer do case Process.get(:ash_tracer) do @@ -215,9 +239,9 @@ defmodule Ash do end end - @doc """ - Gets the current authorize? from the process dictionary - """ + @doc deprecated: """ + Gets the current authorize? from the process dictionary + """ @spec get_authorize?() :: term() def get_authorize? do case Process.get(:ash_authorize?) do @@ -229,9 +253,9 @@ defmodule Ash do end end - @doc """ - Sets tenant into the process dictionary that is used for all changesets and queries. - """ + @doc deprecated: """ + Sets tenant into the process dictionary that is used for all changesets and queries. + """ @spec set_tenant(String.t()) :: :ok def set_tenant(tenant) do Process.put(:ash_tenant, {:tenant, tenant}) @@ -239,9 +263,9 @@ defmodule Ash do :ok end - @doc """ - Gets the current tenant from the process dictionary - """ + @doc deprecated: """ + Gets the current tenant from the process dictionary + """ @spec get_tenant() :: term() def get_tenant do case Process.get(:ash_tenant) do @@ -253,9 +277,9 @@ defmodule Ash do end end - @doc """ - Gets the current context from the process dictionary - """ + @doc deprecated: """ + Gets the current context from the process dictionary + """ @spec get_context() :: term() def get_context do Process.get(:ash_context, %{}) || %{} diff --git a/lib/ash/actions/flows/read.ex b/lib/ash/actions/flows/read.ex deleted file mode 100644 index 8a0fde05..00000000 --- a/lib/ash/actions/flows/read.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule Ash.Actions.Flows.Read do - @moduledoc """ - Execute a read action. - """ - require Ash.Flags - use Ash.Flow - - flow do - argument :query, :struct do - allow_nil? false - constraints instance_of: Ash.Query - end - - argument :action, :struct do - allow_nil? false - constraints instance_of: Ash.Resource.Actions.Read - end - - returns :fake_result - end - - steps do - custom :fake_result, Ash.Actions.Flows.Read.FakeResult do - input %{query: arg(:query), action: arg(:action)} - end - end - - @dialyzer {:no_return, [run: 3]} - def run(query, action, opts) do - Ash.Flags.assert!(:read_uses_flow?, true) - - __MODULE__ - |> Ash.Flow.run(%{query: query, action: action}, opts) - |> case do - result when result.errors == [] -> - {:ok, result.result} - - result -> - {:error, result} - end - end -end diff --git a/lib/ash/actions/flows/read/fake_result.ex b/lib/ash/actions/flows/read/fake_result.ex deleted file mode 100644 index a336004a..00000000 --- a/lib/ash/actions/flows/read/fake_result.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule Ash.Actions.Flows.Read.FakeResult do - @moduledoc """ - Generates a fake result, as the flow has to actually return something. - """ - use Ash.Flow.Step - - def run(_input, _opts, _context) do - {:ok, []} - end -end diff --git a/lib/ash/actions/read/read.ex b/lib/ash/actions/read/read.ex index 01de91b0..fe7679a6 100644 --- a/lib/ash/actions/read/read.ex +++ b/lib/ash/actions/read/read.ex @@ -70,56 +70,52 @@ defmodule Ash.Actions.Read do | {:error, term} def run(query, action, opts \\ []) - if Ash.Flags.read_uses_flow?() do - def run(query, action, opts), do: Ash.Actions.Flows.Read.run(query, action, opts) - else - def run(query, action, opts) do - {query, opts} = Ash.Actions.Helpers.add_process_context(query.api, query, opts) + def run(query, action, opts) do + {query, opts} = Ash.Actions.Helpers.add_process_context(query.api, query, opts) - Ash.Tracer.span :action, - Ash.Api.Info.span_name(query.api, query.resource, action.name), - opts[:tracer] do - metadata = %{ - api: query.api, - resource: query.resource, - resource_short_name: Ash.Resource.Info.short_name(query.resource), - actor: opts[:actor], - tenant: opts[:tenant], - action: action.name, - authorize?: opts[:authorize?] - } + Ash.Tracer.span :action, + Ash.Api.Info.span_name(query.api, query.resource, action.name), + opts[:tracer] do + metadata = %{ + api: query.api, + resource: query.resource, + resource_short_name: Ash.Resource.Info.short_name(query.resource), + actor: opts[:actor], + tenant: opts[:tenant], + action: action.name, + authorize?: opts[:authorize?] + } - Ash.Tracer.telemetry_span [:ash, Ash.Api.Info.short_name(query.api), :read], metadata do - Ash.Tracer.set_metadata(opts[:tracer], :action, metadata) + Ash.Tracer.telemetry_span [:ash, Ash.Api.Info.short_name(query.api), :read], metadata do + Ash.Tracer.set_metadata(opts[:tracer], :action, metadata) - run_around_transaction_hooks(query, fn query -> - case do_run(query, action, opts) do - {:error, error} -> - if opts[:tracer] do - stacktrace = - case error do - %{stacktrace: %{stacktrace: stacktrace}} -> - stacktrace || [] + run_around_transaction_hooks(query, fn query -> + case do_run(query, action, opts) do + {:error, error} -> + if opts[:tracer] do + stacktrace = + case error do + %{stacktrace: %{stacktrace: stacktrace}} -> + stacktrace || [] - _ -> - {:current_stacktrace, stacktrace} = - Process.info(self(), :current_stacktrace) + _ -> + {:current_stacktrace, stacktrace} = + Process.info(self(), :current_stacktrace) - stacktrace - end + stacktrace + end - Ash.Tracer.set_handled_error(opts[:tracer], Ash.Error.to_error_class(error), - stacktrace: stacktrace - ) - end + Ash.Tracer.set_handled_error(opts[:tracer], Ash.Error.to_error_class(error), + stacktrace: stacktrace + ) + end - {:error, error} + {:error, error} - other -> - other - end - end) - end + other -> + other + end + end) end end end @@ -3106,11 +3102,14 @@ defmodule Ash.Actions.Read do resource: destination_resource, context: %{ data_layer: %{lateral_join_source: {root_data, path}} + }, + action: %{ + name: read_action } }, query ) do - case Ash.Query.Aggregate.new(destination_resource, :count, :count) do + case Ash.Query.Aggregate.new(destination_resource, :count, :count, read_action: read_action) do {:ok, aggregate} -> Ash.DataLayer.run_aggregate_query_with_lateral_join( query, diff --git a/lib/ash/api/dsl.ex b/lib/ash/api/dsl.ex index 21531e98..653961ea 100644 --- a/lib/ash/api/dsl.ex +++ b/lib/ash/api/dsl.ex @@ -144,15 +144,7 @@ defmodule Ash.Api.Dsl do Ash.Api.Verifiers.ValidateRelatedResourceInclusion ] - @moduledoc """ - Apis are the entrypoints for working with your resources. - - Apis may optionally include a list of resources, in which case they can be - used as an `Ash.Registry` in various places. This is for backwards compatibility, - but if at all possible you should define an `Ash.Registry` if you are using an extension - that requires a list of resources. For example, most extensions look for two application - environment variables called `:ash_apis` and `:ash_registries` to find any potential registries - """ + @moduledoc false use Spark.Dsl.Extension, sections: @sections, verifiers: @verifiers end diff --git a/lib/ash/api/global_interface.ex b/lib/ash/api/global_interface.ex index 2ece62d3..b6e85955 100644 --- a/lib/ash/api/global_interface.ex +++ b/lib/ash/api/global_interface.ex @@ -1,110 +1,8 @@ defmodule Ash.Api.GlobalInterface do - @moduledoc "The interface for calling any Ash api. Use `Ash` to call these functions." - for {function, arity} <- Ash.Api.Functions.functions() do - if function == :load do - def load({:ok, result}, load) do - load(result, load) - end + @moduledoc false - def load({:error, error}, _), do: {:error, error} - - def load([], _), do: {:ok, []} - def load(nil, _), do: {:ok, nil} - - def load(%page_struct{results: []} = page, _) - when page_struct in [Ash.Page.Keyset, Ash.Page.Offset] do - {:ok, page} - end - end - - if function == :load! do - def load!({:ok, result}, load) do - {:ok, load!(result, load)} - end - - def load!({:error, error}, _), do: raise(Ash.Error.to_error_class(error)) - def load!([], _), do: [] - def load!(nil, _), do: nil - - def load!(%page_struct{results: []} = page, _) - when page_struct in [Ash.Page.Keyset, Ash.Page.Offset] do - page - end - end - - args = Macro.generate_arguments(arity, __MODULE__) - - docs_arity = - if function in Ash.Api.Functions.no_opts_functions() do - arity - else - arity + 1 - end - - @doc "Calls `c:Ash.Api.#{function}/#{docs_arity}` on the resource's configured api. See those callback docs for more." - def unquote(function)(unquote_splicing(args)) do - resource = resource_from_args!(unquote(function), unquote(arity), [unquote_splicing(args)]) - - api = Ash.Resource.Info.api(resource) - - if !api do - raise_no_api_error!(resource, unquote(function), unquote(arity)) - end - - apply(api, unquote(function), [unquote_splicing(args)]) - end - - unless function in Ash.Api.Functions.no_opts_functions() do - args = Macro.generate_arguments(arity + 1, __MODULE__) - - if function == :load! do - def load!({:ok, result}, load, opts) do - {:ok, load(result, load, opts)} - end - - def load!({:error, error}, _, _), do: raise(Ash.Error.to_error_class(error)) - - def load!(nil, _, _), do: nil - def load!([], _, _), do: [] - - def load!(%page_struct{results: []} = page, _, _) - when page_struct in [Ash.Page.Keyset, Ash.Page.Offset] do - page - end - end - - if function == :load do - def load({:ok, result}, load, opts) do - load(result, load, opts) - end - - def load({:error, error}, _, _), do: {:error, error} - def load([], _, _), do: {:ok, []} - def load(nil, _, _), do: {:ok, nil} - - def load(%page_struct{results: []} = page, _, _) - when page_struct in [Ash.Page.Keyset, Ash.Page.Offset] do - {:ok, page} - end - end - - @doc "Calls `c:Ash.Api.#{function}/#{arity + 1}` on the resource's configured api. See those callback docs for more." - def unquote(function)(unquote_splicing(args)) do - resource = - resource_from_args!(unquote(function), unquote(arity), [unquote_splicing(args)]) - - api = Ash.Resource.Info.api(resource) - - if !api do - raise_no_api_error!(resource, unquote(function), unquote(arity)) - end - - apply(api, unquote(function), [unquote_splicing(args)]) - end - end - end - - defp raise_no_api_error!(resource, function, arity) do + @doc false + def raise_no_api_error!(resource, function, arity) do raise ArgumentError, """ No api configured for resource #{inspect(resource)}. @@ -114,7 +12,8 @@ defmodule Ash.Api.GlobalInterface do """ end - defp resource_from_args!(fun, _, [data | _]) when fun in [:load, :load!] do + @doc false + def resource_from_args!(fun, _, [data | _]) when fun in [:load, :load!] do case data do %struct{rerun: {%Ash.Query{resource: resource}, _}} when struct in [Ash.Page.Keyset, Ash.Page.Offset] -> @@ -138,20 +37,20 @@ defmodule Ash.Api.GlobalInterface do end end - defp resource_from_args!(:reload, _, [%resource{} | _]) do + def resource_from_args!(:reload, _, [%resource{} | _]) do resource end - defp resource_from_args!(fun, _, [_, resource | _]) when fun in [:bulk_create, :bulk_create!] do + def resource_from_args!(fun, _, [_, resource | _]) when fun in [:bulk_create, :bulk_create!] do resource end - defp resource_from_args!(fun, _, [resource | _]) - when fun in [:calculate, :calculate!, :get, :get!] do + def resource_from_args!(fun, _, [resource | _]) + when fun in [:calculate, :calculate!, :get, :get!] do resource end - defp resource_from_args!(fun, _, [page | _]) when fun in [:page, :page!] do + def resource_from_args!(fun, _, [page | _]) when fun in [:page, :page!] do case page do %struct{rerun: {%Ash.Query{resource: resource}, _}} when struct in [Ash.Page.Keyset, Ash.Page.Offset] -> @@ -170,8 +69,8 @@ defmodule Ash.Api.GlobalInterface do end end - defp resource_from_args!(fun, _, [query_or_changeset_or_action | _]) - when fun in [:can, :can?] do + def resource_from_args!(fun, _, [query_or_changeset_or_action | _]) + when fun in [:can, :can?] do case query_or_changeset_or_action do %struct{resource: resource} when struct in [Ash.Changeset, Ash.Query, Ash.ActionInput] -> resource @@ -184,42 +83,42 @@ defmodule Ash.Api.GlobalInterface do end end - defp resource_from_args!(fun, _arity, [changeset_or_query | _]) - when fun in [ - :destroy, - :update, - :destroy!, - :update!, - :read, - :read!, - :stream, - :stream!, - :create, - :create!, - :run_action, - :run_action!, - :read_one, - :read_one!, - :get, - :count, - :count!, - :first, - :first!, - :sum, - :sum!, - :min, - :min!, - :max, - :max!, - :avg, - :avg!, - :exists, - :exists?, - :list, - :list!, - :aggregate, - :aggregate! - ] do + def resource_from_args!(fun, _arity, [changeset_or_query | _]) + when fun in [ + :destroy, + :update, + :destroy!, + :update!, + :read, + :read!, + :stream, + :stream!, + :create, + :create!, + :run_action, + :run_action!, + :read_one, + :read_one!, + :get, + :count, + :count!, + :first, + :first!, + :sum, + :sum!, + :min, + :min!, + :max, + :max!, + :avg, + :avg!, + :exists, + :exists?, + :list, + :list!, + :aggregate, + :aggregate! + ] do case changeset_or_query do %struct{resource: resource} when struct in [Ash.Changeset, Ash.Query, Ash.ActionInput] -> resource @@ -236,7 +135,7 @@ defmodule Ash.Api.GlobalInterface do end end - defp resource_from_args!(fun, arity, _args) do + def resource_from_args!(fun, arity, _args) do raise ArgumentError, "Could not determine resource from arguments to `Ash.#{fun}/#{arity}`" end end diff --git a/lib/ash/changeset/changeset.ex b/lib/ash/changeset/changeset.ex index 944ccaf0..56eb246e 100644 --- a/lib/ash/changeset/changeset.ex +++ b/lib/ash/changeset/changeset.ex @@ -2718,6 +2718,20 @@ defmodule Ash.Changeset do end end + def get_argument(changeset, argument) when is_binary(argument) do + changeset.arguments + |> Enum.find(fn {key, _} -> + to_string(key) == argument + end) + |> case do + {_key, value} -> + value + + _ -> + nil + end + end + @doc "Fetches the value of an argument provided to the changeset or `:error`." @spec fetch_argument(t, atom) :: {:ok, term} | :error def fetch_argument(changeset, argument) when is_atom(argument) do @@ -2733,6 +2747,20 @@ defmodule Ash.Changeset do end end + def fetch_argument(changeset, argument) when is_binary(argument) do + changeset.arguments + |> Enum.find(fn {key, _} -> + to_string(key) == argument + end) + |> case do + {_key, value} -> + {:ok, value} + + _ -> + :error + end + end + @doc "Gets the changing value or the original value of an attribute." @spec get_attribute(t, atom) :: term def get_attribute(changeset, attribute) do diff --git a/lib/ash/engine/engine.ex b/lib/ash/engine/engine.ex index 99566a2b..bd233bfb 100644 --- a/lib/ash/engine/engine.ex +++ b/lib/ash/engine/engine.ex @@ -1,49 +1,5 @@ defmodule Ash.Engine do - @moduledoc """ - The Ash engine handles the parallelization/running of requests to Ash. - - Much of the complexity of this doesn't come into play for simple requests. - The way it works is that it accepts a list of `Ash.Engine.Request` structs. - Some of values on those structs will be instances of `Ash.Engine.Request.UnresolvedField`. - These unresolved fields can express a dependence on the field values from other requests. - This allows the engine to wait on executing some code until it has its required inputs, - or if all of its dependencies are met, it can execute it immediately. The engine's job is - to resolve its unresolved fields in the proper order, potentially in parallel. - It also has knowledge baked in about certain special fields, like `data` which is the - field we are ultimately trying to resolve, and `query` which is the field that drives authorization - for read requests. Authorization is done on a *per engine request* basis. - - As the complexity of a system grows, it becomes very difficult to write code that - is both imperative and performant. This is especially true of a framework that is - designed to be configurable. What exactly is done, as well as the order it is done in, - and whether or not is can be parallelized, varies wildly based on factors like how - the resources are configured and what capabilities the data layer has. By implementing - a generic "parallel engine", we can let the engine solve that problem. We only - have to express the various operations that must happen, and what other pieces of data - they need in order to happen, and the engine handles the rest. - - There are various tradeoffs in the current design. The original version of the engine started a process - for each request. While this had the least constrained performance characteristics of all the designs, - it was problematic for various reasons. The primary reason being that it could deadlock without any - reasonable way to debug said deadlock because the various states were distributed. The second version - of the engine introduced a central `Engine` process that helped with some of these issues, but ultimately - had the same problem. The third (and current) version of the engine is reworked instead to be drastically - simpler, potentially at the expense of performance for some requests. Instead of starting a process per - request, it opts to only parallelize the `data` field resolution of fields that are marked as `async?: true`, - (unlike the previous versions which started a process for the whole request.) Although it does its best - to prioritize starting any async tasks, it is possible that if some mix of async/sync requests are passed in - a potentially long running sync task could prevent it from starting an async task, giving this potentially worse - performance characteristics. In practice, this doesn't really matter because the robust data layers support running - asynchronously, unless they are in a transaction in which case everything runs serially anyway. - - The current version of the engine can be seen as an event loop that will async some events and yield them. It also - has support for a concurrency limit (per engine invocation, not globally, although that could now be added much more - easily). This limit defaults to `2 * schedulers_online`. - - Check out the docs for `Ash.Engine.Request` for some more information. This is a private - interface at the moment, though, so this documentation is just here to explain how it works - it is not intended to give you enough information to use the engine directly. - """ + @moduledoc false defstruct [ :ref, diff --git a/lib/ash/engine/request.ex b/lib/ash/engine/request.ex index e0029d2c..29320c9b 100644 --- a/lib/ash/engine/request.ex +++ b/lib/ash/engine/request.ex @@ -1,9 +1,5 @@ defmodule Ash.Engine.Request do - @moduledoc """ - Represents an individual request to be processed by the engine. - - See `new/1` for more information - """ + @moduledoc false defstruct [ :async?, :resource, @@ -50,9 +46,7 @@ defmodule Ash.Engine.Request do require Logger defmodule UnresolvedField do - @moduledoc """ - Represents an unresolved field to be resolved by the engine. - """ + @moduledoc false defstruct [:resolver, deps: [], data?: false] @type t :: %__MODULE__{} diff --git a/lib/ash/flags.ex b/lib/ash/flags.ex index 28c57264..b7d38a1f 100644 --- a/lib/ash/flags.ex +++ b/lib/ash/flags.ex @@ -1,13 +1,7 @@ defmodule Ash.Flags do - @moduledoc """ - Feature flagging support for Ash internals. - - These are macros so that they can be used at compile time to switch code - paths. - """ + @moduledoc false @flags [ - read_uses_flow?: false, ash_three?: false ] @@ -19,14 +13,6 @@ defmodule Ash.Flags do @noop {:__block__, [], []} - @doc "Should read actions use the new flow-based executor?" - @spec read_uses_flow? :: Macro.t() - defmacro read_uses_flow? do - quote do - unquote(Map.get(@flag_values, :read_uses_flow?)) - end - end - @doc "Should we activate Ash 3.0 features?" @spec ash_three? :: Macro.t() defmacro ash_three? do diff --git a/lib/ash/flow/dsl.ex b/lib/ash/flow/dsl.ex index 95248be2..f7f00443 100644 --- a/lib/ash/flow/dsl.ex +++ b/lib/ash/flow/dsl.ex @@ -349,14 +349,7 @@ defmodule Ash.Flow.Dsl do @sections [@flow, @steps] - @moduledoc """ - The built in flow DSL. - - ## Halting - - Steps can be halted, which will stop the flow from continuing and return a halted flow. To attach a specific reason, use a `halt_reason`. - If you need more complex halting logic, then you'd want to use a custom step, and return `{:error, Ash.Error.Flow.Halted.exception(...)}` - """ + @moduledoc false use Spark.Dsl.Extension, sections: @sections, diff --git a/lib/ash/policy/check/accessing_from.ex b/lib/ash/policy/check/accessing_from.ex index 122e8843..7d1aab6e 100644 --- a/lib/ash/policy/check/accessing_from.ex +++ b/lib/ash/policy/check/accessing_from.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.AccessingFrom do - @moduledoc false + @moduledoc "This check is true when the current action is being run \"through\" a relationship." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/action.ex b/lib/ash/policy/check/action.ex index 895808b5..ca8144f2 100644 --- a/lib/ash/policy/check/action.ex +++ b/lib/ash/policy/check/action.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.Action do - @moduledoc false + @moduledoc "This check is true when the action name matches the provided action name." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/action_type.ex b/lib/ash/policy/check/action_type.ex index c6a4e236..847636f9 100644 --- a/lib/ash/policy/check/action_type.ex +++ b/lib/ash/policy/check/action_type.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.ActionType do - @moduledoc false + @moduledoc "This check is true when the action type matches the provided type" use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/actor_attribute_equals.ex b/lib/ash/policy/check/actor_attribute_equals.ex index 40c97362..2d865d35 100644 --- a/lib/ash/policy/check/actor_attribute_equals.ex +++ b/lib/ash/policy/check/actor_attribute_equals.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.ActorAttributeEquals do - @moduledoc false + @moduledoc "This check is true when the value of the specified attribute of the actor equals the specified value." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/actor_present.ex b/lib/ash/policy/check/actor_present.ex index b35fa0e2..10f9bef9 100644 --- a/lib/ash/policy/check/actor_present.ex +++ b/lib/ash/policy/check/actor_present.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.ActorPresent do - @moduledoc false + @moduledoc "This check is true when there is an actor specified, and false when the actor is `nil`." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/attribute.ex b/lib/ash/policy/check/attribute.ex index d8b68946..63d34c43 100644 --- a/lib/ash/policy/check/attribute.ex +++ b/lib/ash/policy/check/attribute.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.Attribute do - @moduledoc false + @moduledoc "This check is true when a field on the record matches a specific filter." use Ash.Policy.FilterCheck diff --git a/lib/ash/policy/check/changing_attributes.ex b/lib/ash/policy/check/changing_attributes.ex index 10cf5ad3..4c37178b 100644 --- a/lib/ash/policy/check/changing_attributes.ex +++ b/lib/ash/policy/check/changing_attributes.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.ChangingAttributes do - @moduledoc false + @moduledoc "This check is true when attribute changes correspond to the provided options." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/changing_relationships.ex b/lib/ash/policy/check/changing_relationships.ex index 860bdb36..bd173cee 100644 --- a/lib/ash/policy/check/changing_relationships.ex +++ b/lib/ash/policy/check/changing_relationships.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.ChangingRelationships do - @moduledoc false + @moduledoc "This check is true when the specified relationship is changing" use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/context_equals.ex b/lib/ash/policy/check/context_equals.ex index c88f4d62..d3c9ca6b 100644 --- a/lib/ash/policy/check/context_equals.ex +++ b/lib/ash/policy/check/context_equals.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.ContextEquals do - @moduledoc false + @moduledoc "This check is true when the value of the specified key or path in the changeset or query context equals the specified value." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/filtering_on.ex b/lib/ash/policy/check/filtering_on.ex index a87cb031..091293ef 100644 --- a/lib/ash/policy/check/filtering_on.ex +++ b/lib/ash/policy/check/filtering_on.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.FilteringOn do - @moduledoc false + @moduledoc "This check is true when the field provided is being referenced anywhere in a filter statement." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/loading.ex b/lib/ash/policy/check/loading.ex index 18be7c57..6fd1e85e 100644 --- a/lib/ash/policy/check/loading.ex +++ b/lib/ash/policy/check/loading.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.Loading do - @moduledoc false + @moduledoc "This check is true when the field or relationship, or path to field, is being loaded and false when it is not." use Ash.Policy.SimpleCheck require Logger diff --git a/lib/ash/policy/check/relates_to_actor_via.ex b/lib/ash/policy/check/relates_to_actor_via.ex index 40bbcf50..6f5bfb8e 100644 --- a/lib/ash/policy/check/relates_to_actor_via.ex +++ b/lib/ash/policy/check/relates_to_actor_via.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.RelatesToActorVia do - @moduledoc false + @moduledoc "This check passes if the data relates to the actor via the specified relationship or path of relationships." use Ash.Policy.FilterCheckWithContext require Ash.Expr diff --git a/lib/ash/policy/check/relating_to_actor.ex b/lib/ash/policy/check/relating_to_actor.ex index a8dee48e..6e8d803f 100644 --- a/lib/ash/policy/check/relating_to_actor.ex +++ b/lib/ash/policy/check/relating_to_actor.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.RelatingToActor do - @moduledoc false + @moduledoc "This check is true when the specified relationship is being changed to the current actor." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/selecting.ex b/lib/ash/policy/check/selecting.ex index d5ffdac7..de365048 100644 --- a/lib/ash/policy/check/selecting.ex +++ b/lib/ash/policy/check/selecting.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.Selecting do - @moduledoc false + @moduledoc "This check is true when the field is being selected and false when it is not." use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/check/static.ex b/lib/ash/policy/check/static.ex index 92fe94a2..0378298d 100644 --- a/lib/ash/policy/check/static.ex +++ b/lib/ash/policy/check/static.ex @@ -1,5 +1,5 @@ defmodule Ash.Policy.Check.Static do - @moduledoc false + @moduledoc "This check is always the result provided" use Ash.Policy.SimpleCheck @impl true diff --git a/lib/ash/policy/info.ex b/lib/ash/policy/info.ex index 6a3c2422..55b0fe29 100644 --- a/lib/ash/policy/info.ex +++ b/lib/ash/policy/info.ex @@ -4,7 +4,6 @@ defmodule Ash.Policy.Info do For more information, see `Ash.Policy.Authorizer` """ - @type request :: Ash.Engine.Request.t() alias Spark.Dsl.Extension diff --git a/lib/ash/process_helpers.ex b/lib/ash/process_helpers.ex new file mode 100644 index 00000000..ff74b188 --- /dev/null +++ b/lib/ash/process_helpers.ex @@ -0,0 +1,77 @@ +defmodule Ash.ProcessHelpers do + @doc """ + Gets all of the ash context so it can be set into a new process. + + Use `transfer_context/1` in the new process to set the context. + """ + @spec get_context_for_transfer(opts :: Keyword.t()) :: term + def get_context_for_transfer(opts \\ []) do + context = Ash.get_context() + actor = Process.get(:ash_actor) + authorize? = Process.get(:ash_authorize?) + tenant = Process.get(:ash_tenant) + tracer = Process.get(:ash_tracer) + + tracer_context = + opts[:tracer] + |> List.wrap() + |> Enum.concat(List.wrap(tracer)) + |> Map.new(fn tracer -> + {tracer, Ash.Tracer.get_span_context(tracer)} + end) + + %{ + context: context, + actor: actor, + tenant: tenant, + authorize?: authorize?, + tracer: tracer, + tracer_context: tracer_context + } + end + + @spec transfer_context(term, opts :: Keyword.t()) :: :ok + def transfer_context( + %{ + context: context, + actor: actor, + tenant: tenant, + authorize?: authorize?, + tracer: tracer, + tracer_context: tracer_context + }, + _opts \\ [] + ) do + case actor do + {:actor, actor} -> + Ash.set_actor(actor) + + _ -> + :ok + end + + case tenant do + {:tenant, tenant} -> + Ash.set_tenant(tenant) + + _ -> + :ok + end + + case authorize? do + {:authorize?, authorize?} -> + Ash.set_authorize?(authorize?) + + _ -> + :ok + end + + Ash.set_tracer(tracer) + + Enum.each(tracer_context || %{}, fn {tracer, tracer_context} -> + Ash.Tracer.set_span_context(tracer, tracer_context) + end) + + Ash.set_context(context) + end +end diff --git a/lib/ash/query/aggregate.ex b/lib/ash/query/aggregate.ex index 905ef58c..0eff721a 100644 --- a/lib/ash/query/aggregate.ex +++ b/lib/ash/query/aggregate.ex @@ -145,7 +145,7 @@ defmodule Ash.Query.Aggregate do build_opts -> Ash.Query.build(related, build_opts) end - read_action = opts[:read_action] || Ash.Resource.Info.primary_action(related, :read).name + read_action = opts[:read_action] || Ash.Resource.Info.primary_action!(related, :read).name query = if query.__validated_for_action__ != read_action do diff --git a/lib/ash/resource/change/builtins.ex b/lib/ash/resource/change/builtins.ex index 5e8d4f1f..99ce2a8d 100644 --- a/lib/ash/resource/change/builtins.ex +++ b/lib/ash/resource/change/builtins.ex @@ -72,6 +72,11 @@ defmodule Ash.Resource.Change.Builtins do {Ash.Resource.Change.RelateActor, opts} end + @spec debug_log(label :: String.t()) :: Ash.Resource.Change.ref() + def debug_log(label \\ nil) do + {Ash.Resource.Change.DebugLog, label: label} + end + @doc """ Apply an "optimistic lock" on a record being updated or destroyed. diff --git a/lib/ash/resource/change/debug_log.ex b/lib/ash/resource/change/debug_log.ex new file mode 100644 index 00000000..730cd4b0 --- /dev/null +++ b/lib/ash/resource/change/debug_log.ex @@ -0,0 +1,60 @@ +defmodule Ash.Resource.Change.DebugLog do + @moduledoc false + use Ash.Resource.Change + require Logger + + @doc false + @spec change(Ash.Changeset.t(), keyword, Ash.Resource.Change.context()) :: Ash.Changeset.t() + def change(changeset, opts, _) do + label = opts[:label] + debug(changeset.params, "input", label, changeset) + debug(changeset.attributes, "casted attributes", label, changeset) + debug(changeset.arguments, "casted arguments", label, changeset) + + changeset + |> Ash.Changeset.after_transaction(fn changeset, result -> + debug(changeset, "final changeset", label, changeset) + debug(result, "action result", label, changeset) + result + end) + |> Ash.Changeset.around_action(fn changeset, callback -> + try do + callback.(changeset) + rescue + e -> + debug_exception( + Exception.format(:error, e, __STACKTRACE__), + "action raised error", + label, + changeset + ) + + reraise e, __STACKTRACE__ + end + end) + end + + defp debug(stuff, our_label, nil, changeset) do + Logger.debug( + "#{our_label} - #{inspect(changeset.resource)}.#{changeset.action.name}: #{inspect(stuff)}" + ) + end + + defp debug(stuff, our_label, their_label, changeset) do + Logger.debug( + "#{their_label} - #{our_label} - #{inspect(changeset.resource)}.#{changeset.action.name}: #{inspect(stuff)}" + ) + end + + defp debug_exception(stuff, our_label, nil, changeset) do + Logger.debug( + "#{our_label} - #{inspect(changeset.resource)}.#{changeset.action.name}: #{stuff}" + ) + end + + defp debug_exception(stuff, our_label, their_label, changeset) do + Logger.debug( + "#{their_label} - #{our_label} - #{inspect(changeset.resource)}.#{changeset.action.name}: #{stuff}" + ) + end +end diff --git a/lib/ash/type/comparable.ex b/lib/ash/type/comparable.ex index 12883e5a..7b5d67d0 100644 --- a/lib/ash/type/comparable.ex +++ b/lib/ash/type/comparable.ex @@ -26,6 +26,7 @@ defmodule Ash.Type.Comparable do end defimpl Comparable, for: unquote(lr_type) do + @moduledoc false def compare(%unquote(lr_type){ left: unquote(left_expression), right: unquote(right_expression) @@ -48,6 +49,7 @@ defmodule Ash.Type.Comparable do end defimpl Comparable, for: unquote(rl_type) do + @moduledoc false def compare(%unquote(rl_type){ left: unquote(right_expression), right: unquote(left_expression) diff --git a/lib/mix/mermaid.ex b/lib/mix/mermaid.ex index 8fbdd881..3712d5d8 100644 --- a/lib/mix/mermaid.ex +++ b/lib/mix/mermaid.ex @@ -1,7 +1,5 @@ defmodule Mix.Mermaid do - @moduledoc """ - Mermaid Diagram helper functions. - """ + @moduledoc false @doc """ Generate the option string for a mermaid config file if it exists. diff --git a/mix.exs b/mix.exs index c4f73a12..19afa1e9 100644 --- a/mix.exs +++ b/mix.exs @@ -34,85 +34,87 @@ defmodule Ash.MixProject do ] end - defp extras do - [ - "documentation/tutorials/get-started.md", - "documentation/tutorials/why-ash.md", - "documentation/tutorials/philosophy.md", - "documentation/tutorials/using-hexdocs.md", - "documentation/tutorials/extending-resources.md", - "documentation/how_to/contribute.md", - "documentation/how_to/define-idiomatic-actions.md", - "documentation/how_to/defining-manual-relationships.md", - "documentation/how_to/handle-errors.md", - "documentation/how_to/structure-your-project.md", - "documentation/how_to/upgrade.md", - "documentation/how_to/use-without-data-layers.md", - "documentation/how_to/validate-changes.md", - "documentation/how_to/auto-format-code.md", - "documentation/topics/actions.md", - "documentation/topics/aggregates.md", - "documentation/topics/atomics.md", - "documentation/topics/attributes.md", - "documentation/topics/bulk-actions.md", - "documentation/topics/calculations.md", - "documentation/topics/code-interface.md", - "documentation/topics/constraints.md", - "documentation/topics/development-utilities.md", - "documentation/topics/embedded-resources.md", - "documentation/topics/expressions.md", - "documentation/topics/flows.md", - "documentation/topics/glossary.md", - "documentation/topics/identities.md", - "documentation/topics/managing-relationships.md", - "documentation/topics/manual-actions.md", - "documentation/topics/monitoring.md", - "documentation/topics/multitenancy.md", - "documentation/topics/notifiers.md", - "documentation/topics/pagination.md", - "documentation/topics/phoenix.md", - "documentation/topics/policies.md", - "documentation/topics/pub_sub.md", - "documentation/topics/relationships.md", - "documentation/topics/security.md", - "documentation/topics/store-context-in-process.md", - "documentation/topics/testing.md", - "documentation/topics/timeouts.md", - "documentation/topics/validations.md", - "documentation/dsls/DSL:-Ash.Api.md", - "documentation/dsls/DSL:-Ash.DataLayer.Ets.md", - "documentation/dsls/DSL:-Ash.DataLayer.Mnesia.md", - "documentation/dsls/DSL:-Ash.Flow.md", - "documentation/dsls/DSL:-Ash.Notifier.PubSub.md", - "documentation/dsls/DSL:-Ash.Policy.Authorizer.md", - "documentation/dsls/DSL:-Ash.Registry.md", - "documentation/dsls/DSL:-Ash.Resource.md" - ] - end - - defp groups_for_extras do - [ - Tutorials: [ - "documentation/tutorials/get-started.md", - "documentation/tutorials/philosophy.md", - "documentation/tutorials/why-ash.md", - ~r'documentation/tutorials' - ], - "How To": ~r'documentation/how_to', - Topics: ~r'documentation/topics', - DSLs: ~r'documentation/dsls' - ] - end - defp docs do - # The main page in the docs [ main: "get-started", source_ref: "v#{@version}", logo: "logos/small-logo.png", extra_section: "GUIDES", - extras: extras(), - groups_for_extras: groups_for_extras(), + extras: [ + "documentation/tutorials/get-started.md", + "documentation/tutorials/why-ash.md", + "documentation/tutorials/philosophy.md", + "documentation/tutorials/using-hexdocs.md", + "documentation/tutorials/extending-resources.md", + "documentation/how_to/contribute.md", + "documentation/how_to/define-idiomatic-actions.md", + "documentation/how_to/defining-manual-relationships.md", + "documentation/how_to/handle-errors.md", + "documentation/how_to/structure-your-project.md", + "documentation/how_to/upgrade.md", + "documentation/how_to/use-without-data-layers.md", + "documentation/how_to/validate-changes.md", + "documentation/how_to/auto-format-code.md", + "documentation/topics/actions.md", + "documentation/topics/aggregates.md", + "documentation/topics/atomics.md", + "documentation/topics/attributes.md", + "documentation/topics/bulk-actions.md", + "documentation/topics/calculations.md", + "documentation/topics/code-interface.md", + "documentation/topics/constraints.md", + "documentation/topics/development-utilities.md", + "documentation/topics/embedded-resources.md", + "documentation/topics/expressions.md", + "documentation/topics/flows.md", + "documentation/topics/glossary.md", + "documentation/topics/identities.md", + "documentation/topics/managing-relationships.md", + "documentation/topics/manual-actions.md", + "documentation/topics/monitoring.md", + "documentation/topics/multitenancy.md", + "documentation/topics/notifiers.md", + "documentation/topics/pagination.md", + "documentation/topics/phoenix.md", + "documentation/topics/policies.md", + "documentation/topics/pub_sub.md", + "documentation/topics/relationships.md", + "documentation/topics/security.md", + "documentation/topics/store-context-in-process.md", + "documentation/topics/testing.md", + "documentation/topics/timeouts.md", + "documentation/topics/validations.md", + "documentation/dsls/DSL:-Ash.Resource.md", + "documentation/dsls/DSL:-Ash.Api.md", + "documentation/dsls/DSL:-Ash.Notifier.PubSub.md", + "documentation/dsls/DSL:-Ash.Policy.Authorizer.md", + "documentation/dsls/DSL:-Ash.Flow.md", + "documentation/dsls/DSL:-Ash.DataLayer.Ets.md", + "documentation/dsls/DSL:-Ash.DataLayer.Mnesia.md", + "documentation/dsls/DSL:-Ash.Registry.md" + ], + groups_for_extras: [ + Tutorials: ~r'documentation/tutorials', + "How To": ~r'documentation/how_to', + Topics: ~r'documentation/topics', + DSLs: ~r'documentation/dsls' + ], + nest_modules_by_prefix: [ + Ash.Error, + Ash.Flow.Transformers, + Ash.Policy.Authorizer, + Ash.Api.Transformers, + Ash.Api.Verifiers, + Ash.Registry.Transformers, + Ash.Resource.Transformers, + Ash.Resource.Verifiers, + Ash.Registry.ResourceValidations, + Ash.Query.Function, + Ash.Query.Operator, + Ash.Resource.Change, + Ash.Resource.Validation, + Ash.Policy.Check + ], before_closing_head_tag: fn type -> if type == :html do """ @@ -128,86 +130,8 @@ defmodule Ash.MixProject do """ end end, - spark: [ - extensions: [ - %{ - module: Ash.Resource.Dsl, - name: "Resource", - target: "Ash.Resource", - type: "Resource", - default_for_target?: true - }, - %{ - module: Ash.Api.Dsl, - name: "Api", - target: "Ash.Api", - type: "Api", - default_for_target?: true - }, - %{ - module: Ash.DataLayer.Ets, - name: "Ets", - target: "Ash.Resource", - type: "DataLayer" - }, - %{ - module: Ash.DataLayer.Mnesia, - name: "Mnesia", - target: "Ash.Resource", - type: "DataLayer" - }, - %{ - module: Ash.Policy.Authorizer, - name: "Policy Authorizer", - target: "Ash.Resource", - type: "Authorizer" - }, - %{ - module: Ash.Flow.Dsl, - name: "Flow", - target: "Ash.Flow", - type: "Flow", - default_for_target?: true - }, - %{ - module: Ash.Notifier.PubSub, - name: "PubSub", - target: "Ash.Resource", - type: "Notifier" - }, - %{ - module: Ash.Registry.Dsl, - name: "Registry", - target: "Ash.Registry", - type: "Registry", - default_for_target?: true - }, - %{ - module: Ash.Registry.ResourceValidations, - name: "Resource Validations", - type: "Extension", - target: "Ash.Registry" - } - ], - mix_tasks: [ - Charts: [ - Mix.Tasks.Ash.GenerateFlowCharts - ] - ] - ], groups_for_modules: [ - Extensions: [ - Ash.Api, - Ash.Resource, - Ash.DataLayer.Ets, - Ash.DataLayer.Mnesia, - Ash.DataLayer.Simple, - Ash.Notifier.PubSub, - Ash.Policy.Authorizer, - Ash.Registry - ], Resources: [ - Ash.Api, Ash.Filter.TemplateHelpers, Ash.Calculation, Ash.Resource.Calculation.Builtins, @@ -222,6 +146,14 @@ defmodule Ash.MixProject do Ash.Resource.ManualRelationship, Ash.Resource.Attribute.Helpers ], + "Action Input & Interface": [ + Ash, + Ash.Api, + Ash.Query, + Ash.Changeset, + Ash.ActionInput, + Ash.BulkResult + ], Queries: [ Ash.Query, Ash.Resource.Preparation, @@ -244,6 +176,15 @@ defmodule Ash.MixProject do Ash.Policy.FilterCheckWithContext, Ash.Policy.SimpleCheck ], + Extensions: [ + Ash.Resource, + Ash.DataLayer.Ets, + Ash.DataLayer.Mnesia, + Ash.DataLayer.Simple, + Ash.Notifier.PubSub, + Ash.Policy.Authorizer, + Ash.Registry + ], Introspection: [ Ash.Api.Info, Ash.Registry.Info, @@ -252,10 +193,10 @@ defmodule Ash.MixProject do Ash.Policy.Info, Ash.DataLayer.Ets.Info, Ash.DataLayer.Mnesia.Info, - Ash.Notifier.PubSub.Info + Ash.Notifier.PubSub.Info, + ~r/Ash.Api.Dsl.*/ ], Utilities: [ - Ash, Ash.Expr, Ash.Page, Ash.Page.Keyset, @@ -264,24 +205,41 @@ defmodule Ash.MixProject do Ash.Filter.Runtime, Ash.Sort, Ash.CiString, + Ash.Vector, Ash.Union, Ash.UUID, Ash.NotLoaded, + Ash.ForbiddenField, + Ash.NotSelected, Ash.Changeset.ManagedRelationshipHelpers, Ash.DataLayer.Simple, Ash.Filter.Simple, Ash.Filter.Simple.Not, Ash.OptionsHelpers, Ash.Resource.Builder, - Ash.Tracer + Ash.ProcessHelpers, + Ash.Mix.Tasks.Helpers, + Ash.PlugHelpers, + Ash.SatSolver + ], + Visualizations: [ + Ash.Api.Info.Diagram, + Ash.Api.Info.Livebook, + Ash.Policy.Chart.Mermaid ], Testing: [ Ash.Generator, Ash.Seed, Ash.Test ], + Tracing: [ + Ash.Tracer, + Ash.Tracer.Simple, + Ash.Tracer.Simple.Span + ], Flow: [ Ash.Flow, + Ash.Flow.Result, Ash.Flow.Executor, Ash.Flow.Step, Ash.Flow.Chart.Mermaid, @@ -295,8 +253,46 @@ defmodule Ash.MixProject do Ash.Error, ~r/Ash.Error\./ ], - Transformers: [~r/\.Transformers\./, Ash.Registry.ResourceValidations], - Internals: ~r/.*/ + "DSL Transformers": [ + ~r/\.Transformers\./, + ~r/\.Verifiers\./, + Ash.Registry.ResourceValidations + ], + Expressions: [ + Ash.Filter.Predicate, + Ash.Filter.Simple, + Ash.Filter.Simple.Not, + ~r/Ash.Query.Operator/, + ~r/Ash.Query.Function/, + ~r/Ash.Query.Ref/, + Ash.Query.Not, + Ash.Query.Call, + Ash.Query.BooleanExpression, + Ash.Query.Exists, + Ash.Query.Parent + ], + Builtins: [ + ~r/Ash.Resource.Validation/, + ~r/Ash.Resource.Change/, + ~r/Ash.Policy.Check/ + ], + Introspection: [ + ~r/Ash.Resource.Relationships/, + ~r/Ash.Resource.Calculation/, + ~r/Ash.Resource.Interface/, + ~r/Ash.Resource.Identity/, + ~r/Ash.Resource.Attribute/, + ~r/Ash.Resource.Attribute/, + ~r/Ash.Resource.Aggregate/, + ~r/Ash.Resource.Actions/, + ~r/Ash.Flow.Step/, + ~r/Ash.Flow/, + Ash.Mix.Tasks.Helpers, + Ash.Policy.FieldPolicy, + ~r/Ash.Registry/, + Ash.Policy.Policy, + Ash.Notifier.PubSub.Publication + ] ] ] end diff --git a/mix.lock b/mix.lock index afb6e424..ee52cec6 100644 --- a/mix.lock +++ b/mix.lock @@ -8,13 +8,13 @@ "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"}, "earmark": {:hex, :earmark, "1.4.40", "ff1a0f8bf3b298113c2a257c4e7a8b29ba9db5d35f5ee6d29291cb8caa09a071", [:mix], [{:earmark_parser, "~> 1.4.35", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "5fb622d5e36046bc313a426211e8bf769ba50db7720744859a21932c6470d75c"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, - "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "7a53ce9da286d06dd3c190a7a7104e2e47707400", []}, + "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "84c2037f67e07982544bfaa90b8a3471866a20aa", []}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"},