v0.9.0 (2026-09-11)
The planning behind produce, pre_produce, exec and pre_exec was rebuilt.
What you can rely on
- After
produce, every requested entity is in the context, with every requested trait. A request that cannot be honoured fails before anything runs. Previously such a request could return a context silently missing the entity or the trait, or crash halfway through the execution. - Plans that need an alternative are found: another command producing the entity, another route to a trait, a command re-producing an entity after a deleting command consumed it. Previously these requests failed with a false circular-dependency or trait error.
- Entities already in the context are respected: a command re-producing one needs a deleting command in between, and the commands consuming, deleting and re-producing it run in a valid order. Previously that order could depend on command names and crash or hand a command an entity in the wrong state.
- A command runs only once the entities its params name exist and carry the traits their
with_traitsask for at that moment. Previously a planned command could receive an entity a later-planned command had already stripped of the trait, or one it had deleted. - A request is a set: the plan does not depend on the order of its entries or of the traits within an entry. Previously the entity named first could pick a producer the others then had to live with, and the same request written in another order produced different side entities and traits.
pre_produceprepares the dependencies of the requested entities even when producing everything in oneproducecall would be impossible, and never consumes an entity the request names.
What changed for schemas
- The
generatefunctions of params and thegenerate_argsfunctions of trait declarations run once, while the plan is built and before any command executes; the execution reuses the values. Keep them to producing values (random data, counters), without writes to a database or a file. - A trait declared with
fromreplaces the named trait, and a command that later asks for the replaced trait throughwith_traitsis refused, where it used to run. A trait that adds a property without leaving the previous state is declared withoutfrom.
Requirements
- Elixir 1.19 or later. The previous minimum was 1.15.
Errors
A refused request explains every candidate it rejected:
cannot produce entity :contract: no candidate command fits the plan
- :sign_contract would duplicate existing :contract_copy (rebind or delete it first)
- :import_contract would duplicate existing :approval (rebind or delete it first)A plan that would end without a requested trait raises SeedFactory.MissingRequestedTraitError, naming the command whose transition removes the trait.
SeedFactory.ConflictingTraitsError is gone. Two requested traits whose commands would both produce the same entity are refused with the reason also produces :x, already produced by :y in this plan. A request one command can serve on its own now succeeds, where the removed check refused it.
A request asking for a trait together with one that replaces it is refused naming the request. The message used to name a nil command.
Bug fixes
- A requested trait consumed by a transition declared with a
fromlist was silently skipped. It now raises, like a transition declared with a singlefrom. with_traitson an entity param accepted an entity that had gone through the required trait and lost it. It now raises, or uses another candidate when one exists.- Trait resolution gave up when the first option of a
fromlist could not be satisfied. The options are now tried in the declared order. - A trait declared on several commands was reported as a mismatch when one executed command did not add it while another did. Every executed declaration is now reported.
- Input for a param nested in a
param ... do ... endcontainer did not cover the dependency: the command producing it still ran. - The compile-time trait cycle check missed a cycle through a
fromedge of a shadowed declaration. Such a schema compiled and could hangproduce.
v0.8.2 (2026-08-18)
Bug Fixes
- Fix trait resolution depending on the order of traits in a
producerequest. A trait declared on a single command now wins conflict resolution regardless of its position in the list, and conflicting trait requests raiseSeedFactory.TraitResolutionErrorinstead of silently producing the entity through another command. - Fix missing execution-order edges after conflict resolution removes a producer another node depended on. Could surface as
KeyErrordepending on the topological sort tie-break. - Fix
producesilently returning an incomplete context when the selected commands form a circular dependency. Now raisesSeedFactory.CircularDependencyErrornaming the commands. - Fix
with_traitson an entity param being ignored when selecting the producer of an absent dependency while the demanding command was still in unresolved conflict groups. Such a demand now wins conflict resolution once the demanding command settles in the plan, and expires when that command leaves it.
v0.8.1 (2026-08-17)
Bug Fixes
- Fix
FunctionClauseErrorwhen producing a second instance of an entity via rebinding and every producing command would duplicate an existing entity. Now raisesSeedFactory.EntityAlreadyExistsErrornaming the entity that must be rebound. - Fix missing execution-order edges for commands that are re-executed under rebinding. Could surface as
KeyErrordepending on topological sort tie-break. - Fix args of a trait declared on several commands leaking into the command that did not declare them.
producethen failed with "Input doesn't match defined params".
v0.8.0 (2026-03-29)
Features
- Add
SeedFactory.ExecError— structured exception for resolver failures with execution plan, trails, and current traits - Add execution history to meta (
execution_history) that records all produce/exec calls with caller, rebinding, and executed commands - Add
Context.track_execution/3for automatic execution tracking - Add custom
Inspectfor SeedFactory.Execution with compact#execution[...]format
Breaking Changes
- Resolver
{:error, error}now raisesSeedFactory.ExecErrorinstead ofRuntimeError
Performance
- Replace
libgraphdependency with inline topological sort - Optimize compile-time trait indexing
- Fix quadratic list accumulation in
scan_subsequent_traits
Improvements
- Improved documentation for core functions,
SeedFactory.Schema, andSeedFactory.Testmodules
v0.7.1 (2026-03-26)
Bug Fixes
- Fix trait-resolved node being overwritten by later conflict resolution
- Fix warning from ex_docs
- Fix flaky test
Improvements
- Skip coverage for test/support files
- Update dependencies
v0.7.0 (2026-01-31)
Features
- Same trait can be set by multiple commands
- "Did you mean?" suggestions in compile-time errors for typos
Compile-time Validations
- Validate referenced entities in params
- Validate circular trait dependencies
- Validate trait references in
:fromoption (non-existent trait, wrong entity)
Bug Fixes
- Fix trait transition execution order
- Fix trait resolution for commands in conflict groups
- Fix
:is_subsetconflict resolution removing commands in multiple groups - Fix trait resolution when command produces multiple entities
Improvements
- Major internal refactoring - extracted modules:
Context,Requirements,CommandGraph,Exceptions - Improved exception messages with better context
- Improved error reporting
- Better error message when produced entity was already produced by another command
- Hide
__spark_metadata__from inspect output - Compile test support files
Dependencies
- Updated Spark and other dependencies
v0.6.0 (2024-05-17)
Features
:fromoption now accepts list of traits - a trait can replace multiple parent traits:trait :suspended, :user do from [:pending, :active] # Can transition from either state exec :suspend_user end
Bug Fixes
- Match generated args by
args_matchfunction when there's a conflict - fixes cases whengenerate_argshas randomness - Don't treat structs as maps in deep comparison/merging
Improvements
- Better error message when entity was put manually into context without using SeedFactory
Breaking Changes
- Requires Elixir ~> 1.15 (due to Spark ~> 2.1 dependency)
v0.5.0 (2024-03-12)
Features
- Multiple commands can produce the same entity - the first defined command becomes the default
- Automatic command selection based on trait restrictions - when traits require a specific command that produces the entity, SeedFactory automatically switches to that command
Improvements
- Better conflict resolution when merging trait arguments
- Validate entity existence in
rebind/3- raisesArgumentErrorfor unknown entities - Custom Inspect implementation for SeedFactory.Command for cleaner output
Bug Fixes
- Fix producing entity with traits when it already exists without traits
Error Messages
- Show command name when merging of args fails
- Show command name in errors when entity doesn't exist
- Helpful message when
produceis called with 1 argument incorrectly - Disallow nil command names
Validations
:with_traitsoption must be used only for parameters with type:entity
v0.4.0 (2023-08-16)
Features
- Schema composition - include other schemas using
include_schema MyApp.OtherSchemato reuse commands and traits generate_argsandargs_matchoptions for traits - more flexible alternative toargs_patternfor dynamic trait matching:trait :expired, :project do exec :publish_project do args_match(fn args -> Date.compare(Date.utc_today(), args.expiry_date) == :gt end) generate_args(fn -> %{start_date: Date.add(today, -22), expiry_date: Date.add(today, -1)} end) end end
Improvements
:fromoption is now optional forproduceandupdateinstructions - defaults to entity name- Allow
param/1macro without parentheses in formatter
v0.3.0 (2023-07-23)
Features
- Traits support - labels assigned to entities when specific commands are executed. Allows requesting entities with specific traits via
produce(context, user: [:admin, :active]) pre_exec/3- creates dependencies needed to execute a command, useful when executing the same command multiple timespre_produce/2- produces dependencies needed for specified entities
Breaking Changes
- DSL syntax for parameters changed:
# Before (v0.2.0) param :name, &Faker.Person.name/0 param :role, fn -> :normal end param :company, :company # After (v0.3.0) param :name, generate: &Faker.Person.name/0 param :role, value: :normal param :company, entity: :company - Renamed internal
:commandsDSL section to:root
Improvements
- Friendly error when unknown command is passed to
exec - Raise error if unknown entity is passed to
produce - Allow rebinding to the same value (no longer raises)
- Compile-time validations for traits
- Exported formatter settings for
exec/1-2andfrom/1
v0.2.0 (2023-06-07)
Features
- Support nested rebinding -
rebind/3can now be nested, merging rebindings at each level and properly restoring previous state after callback completion. Raises on rebinding conflicts.
Improvements
- Raise an error when redundant keys are passed to a command (keys not defined in params)
v0.1.0 (2023-05-06)
Initial release.
Features
- Schema DSL for defining entities and commands
- Command system with:
produce- for creating entitiesupdate- for modifying entitiesdelete- for removing entities
- Parameter handling with support for generating values and dependencies on other entities
- Test utilities (
SeedFactory.Test) for using in ExUnit tests - Compile-time verification of dependencies between commands and entities