SparkMeta
Generic Spark DSL walker and introspection library. Provides a unified way to inspect any Spark-based DSL module — extracting extensions, entities, options, and persisted state — with an opt-in handler system for richer, extension-specific output.
Features
-
Walk any Spark DSL module into a normalized
DslStatestruct - Query entities, options, and persisted values without knowing the DSL internals
-
Register per-extension handlers (
SparkMeta.Extension) for richer extraction -
Pluggable analyzer pipeline (
SparkMeta.Analyzer) that produces a typedAnalysisstruct - Built-in analyzers for Ash resources, AshAuthentication strategies, AshStateMachine, and module docs
-
Thread-safe
Registrybacked by ETS for hot-path handler lookups - Optional source provider for attaching source text/AST to the analysis context
Installation
def deps do
[
{:spark_meta, "~> 0.1"}
]
endQuick Start
# Walk a Spark module into a raw DslState
{:ok, state} = SparkMeta.walk(MyApp.Accounts.User)
state.extensions # => [Ash.Resource, ...]
state.sections # => %{[:attributes] => [%Ash.Resource.Attribute{...}, ...], ...}
state.options # => %{[:attributes] => %{allow_nil?: false, ...}, ...}
state.persisted # => %{domain: MyApp.Domain, ...}
state.extension_data # => %{Ash.Resource => %{attributes: [...], actions: [...], ...}}Analyzer Pipeline
SparkMeta.analyze/2 runs a configurable pipeline of SparkMeta.Analyzer modules and returns a typed Analysis struct with extracted facts and any non-fatal diagnostics.
{:ok, analysis} = SparkMeta.analyze(MyApp.Accounts.User)
analysis.facts[:attributes] # => [%{name: :email, type: :string, ...}, ...]
analysis.facts[:actions] # => [%{name: :create, type: :create, ...}, ...]
analysis.diagnostics # => [] (list of non-fatal issue maps)Run with custom analyzers:
{:ok, analysis} = SparkMeta.analyze(MyApp.Accounts.User,
analyzers: SparkMeta.default_analyzers() ++ [MyApp.Analyzers.Compliance]
)Implementing a Custom Analyzer
defmodule MyApp.Analyzers.Compliance do
@behaviour SparkMeta.Analyzer
@impl true
def analyze(%SparkMeta.Context{} = ctx, %SparkMeta.Analysis{} = analysis) do
policies = SparkMeta.entities(ctx.module, [:policies])
analysis =
analysis
|> SparkMeta.Analysis.put_fact(:policy_count, length(policies))
|> SparkMeta.Analysis.put_fact(:has_policies, policies != [])
{:ok, analysis}
end
endRegistering Extension Handlers
Register a handler for a specific Spark extension to enrich DslState.extension_data:
SparkMeta.Registry.register(MyExtension, MyExtensionHandler)
The handler must implement SparkMeta.Extension:
defmodule MyExtensionHandler do
@behaviour SparkMeta.Extension
@impl true
def extract(extension_module, %SparkMeta.DslState{} = dsl_state) do
entities = dsl_state.sections[[:my_extension]] || []
%{items: Enum.map(entities, & &1.name)}
end
end
Handlers are registered automatically at application start if they call SparkMeta.Registry.register/2 inside an Application.start/2 callback. The built-in SparkMeta.Handlers.AshResource handler is registered automatically when Ash is loaded.
Convenience Functions
SparkMeta.spark_module?(module) # => true | false
SparkMeta.extensions(module) # => [MyExt, ...]
SparkMeta.entities(module, [:attributes]) # => [%Ash.Resource.Attribute{...}, ...]
SparkMeta.get_opt(module, [:actions, :defaults], :accept, nil)
SparkMeta.get_persisted(module, :domain, nil)Source Provider
Attach source material to the analysis context for analyzers that need it:
{:ok, analysis} = SparkMeta.analyze(MyResource,
source_provider: SparkMeta.SourceProvider.FileSystem
)
# ctx.source_path, ctx.source_text, ctx.source_ast are populated for analyzers
Implement SparkMeta.SourceProvider to supply source from any backing store.
Built-in Analyzers
| Module | Facts produced |
|---|---|
SparkMeta.Analyzers.ModuleDoc | :moduledoc |
SparkMeta.Analyzers.Extensions | :extensions, :persisted |
SparkMeta.Analyzers.AshResource | :attributes, :relationships, :actions, :policies, :compliance, :telemetry_prefix |
SparkMeta.Analyzers.StateMachine | :states, :events, :transitions |
SparkMeta.Analyzers.AuthStrategies | :auth_strategies |
License
MIT