AshDyan (AshDyan v0.1.0)

Copy Markdown View Source

AshDyan — runtime-driven dynamic analysis for any Ash resource.

AshDyan is a standalone Ash extension (no dependency on ash_phoenix_gen_api) that lets any Ash resource/domain be declared "analyzable", and exposes a runtime function/API where a caller sends a request spec and gets back chart-ready aggregated data.

Positioning

  • Turns "give me a chart of X grouped by Y, filtered by Z" into a generic, safe, reusable runtime capability across any Ash resource — instead of writing a bespoke aggregate action per chart.
  • It is not a full BI/reporting engine, not a query builder UI, and not tied to Phoenix/Channels. Delivery (HTTP controller, Channel, LiveView, gen_api mfa) is a thin adapter on top.

Security model

The dyan DSL is a whitelist. A runtime request can only reference fields, functions, buckets, and filter targets declared there — this is what makes "arbitrary column + arbitrary filter from the client" safe rather than an injection/DoS vector.

Entry point

AshDyan.run/1 (or AshDyan.run/2 with an actor for policy checks) is the single entry point. It validates the spec, builds an Ash.Query, runs it through the resource's normal read action (so Ash policies/authorization still apply), and formats the result.

Logging

AshDyan.run/2 emits structured logs via the Logger standard library: a :debug line when a request starts, a :debug line when a request is rejected during validation/configuration, a :warning when the requested analysis type is unsupported by the resource's data layer, and an :error line when the underlying read fails. Filter contents are never logged.

See AshDyan.Request for the request spec shape and AshDyan.Info for introspection helpers.

Summary

Types

Numeric aggregate functions.

Analysis capabilities exposed by AshDyan.

Options for run/2.

Time bucket granularities.

Functions

Run a dynamic analysis request.

Same as run/2 but raises on error.

Returns true if the resource's data layer supports the given capability.

Types

aggregate_function()

@type aggregate_function() ::
  :sum
  | :avg
  | :min
  | :max
  | :count
  | :count_distinct
  | :stddev
  | :variance
  | :median

Numeric aggregate functions.

capability()

@type capability() ::
  :frequency | :aggregate | :time_bucket | :percentile | :histogram

Analysis capabilities exposed by AshDyan.

run_opt()

@type run_opt() ::
  {:actor, term()}
  | {:tenant, term()}
  | {:timeout, pos_integer() | :infinity}
  | {:data, [Ash.Resource.Record.t()]}

Options for run/2.

  • :actor — actor passed to the read action for policy checks.
  • :tenant — tenant for multitenant resources.
  • :timeout — overrides the per-request query timeout (defaults to the resource's query_timeout, which is always enforced).
  • :data — explicit in-memory dataset for the Ash.DataLayer.Simple layer (used by tests and embedded resources).

time_bucket()

@type time_bucket() :: :minute | :hour | :day | :week | :month | :quarter | :year

Time bucket granularities.

Functions

dyan(body)

(macro)

run(spec, opts \\ [])

@spec run(AshDyan.Request.t() | map(), [run_opt()]) ::
  {:ok, AshDyan.Result.t()} | {:error, term()}

Run a dynamic analysis request.

Options

  • :actor — the actor to authorize as (passed to the read action).
  • :tenant — the tenant for multitenant resources.
  • :timeout — overrides the per-request query timeout (defaults to the resource's configured query_timeout, which is always enforced).
  • :data — an explicit in-memory dataset (Ash.DataLayer.Simple only). When supplied, the query reads from this list instead of the data layer. Mostly used by tests and embedded resources.

Returns {:ok, %AshDyan.Result{}} or {:error, error}. Validation and configuration errors are returned as {:error, %AshDyan.Error{}} naming the offending field; read failures are returned as the underlying Ash error.

run!(spec, opts \\ [])

@spec run!(AshDyan.Request.t() | map(), [run_opt()]) :: AshDyan.Result.t()

Same as run/2 but raises on error.

supports?(resource, capability)

@spec supports?(module(), capability()) :: boolean()

Returns true if the resource's data layer supports the given capability.

Capabilities: :frequency, :aggregate, :time_bucket, :percentile, :histogram.

This is surfaced explicitly so callers can discover data-layer limits before issuing a query, rather than discovering them at query time.