Skip to content
Merged

dev #119

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,46 @@
# SPDX-License-Identifier: MIT

# Used by "mix format"
locals_without_parens = [
id: 1,
spark_locals_without_parens = [
calculate: 1,
category: 1,
is_enabled?: 1,
characteristic: 2,
pick: 1,
rename: 1,
field: 3,
expect: 1,
relate: 1,
guard: 1,
customize: 1,
order: 1,
initial_states: 1,
default_initial_state: 1,
state_attribute: 1,
transition: 1,
compact: 1,
label: 1
characteristic: 3,
constraints: 1,
create: 1,
create: 2,
description: 1,
feature: 1,
feature: 2,
id: 1,
is_enabled?: 1,
major_version: 1,
minor_version: 1,
name: 1,
parties: 2,
parties: 3,
party: 2,
party: 3,
patch_version: 1,
place: 2,
place: 3,
places: 2,
places: 3,
reference: 1,
role: 2,
role: 3,
tmf_version: 1,
type: 1,
update: 1,
update: 2
]

[
plugins: [Spark.Formatter],
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:ash],
locals_without_parens: locals_without_parens,
import_deps: [:ash, :ash_jason, :ash_neo4j, :ash_outstanding, :ash_state_machine],
locals_without_parens: spark_locals_without_parens,
export: [
locals_without_parens: locals_without_parens
locals_without_parens: spark_locals_without_parens
]
]
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ diffo-*.tar
.DS_Store

# Agent related
.claude/*
.claude/*
CLAUDE.md
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline

<!-- changelog -->

## [v0.2.2](https://github.com/diffo-dev/diffo/compare/v0.2.1...v0.2.2) (2026-05-08)

## Notable Changes
* Updated to ash_neo4j 0.5.0 with async test support
* Igniter installer — `mix igniter.install diffo` now sets up Neo4j config, custom expressions, and Spark DSL formatter
* Spark DSL formatter configured for all provider extensions; `mix format` enforced across the codebase
* `usage-rules.md` added for AI coding assistant guidance when working with Diffo

## What's Changed
* async tests by @matt-beanland in https://github.com/diffo-dev/diffo/pull/114
* igniter by @matt-beanlanda in https://github.com/diffo-dev/diffo/pull/116
* spark formatter by @matt-beanlanda in https://github.com/diffo-dev/diffo/pull/117
* usage_rules by @matt-beanlanda in https://github.com/diffo-dev/diffo/pull/118

## [v0.2.1](https://github.com/diffo-dev/diffo/compare/v0.2.0...v0.2.1) (2026-05-06)

## Notable Changes
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,25 @@ Diffo is especially suited for use in organisations with loosely coupled 'entity

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `diffo` to your list of dependencies in `mix.exs`:
The recommended way to install Diffo is with [Igniter](https://hexdocs.pm/igniter):

```bash
mix igniter.install diffo
```

This will add the dependency, configure Neo4j (via `ash_neo4j`), register the custom expression, and set up the Spark formatter.

Alternatively, add `diffo` to your list of dependencies in `mix.exs` manually:

```elixir
def deps do
[
{:diffo, "~> 0.1.6"}
{:diffo, "~> 0.2.1"}
]
end
```

You should need [Neo4j](https://github.com/neo4j/neo4j) available. We recommend the Neo4j Community 5 latest, available at [Neo4j Deploymnent Centre](https://neo4j.com/deployment-center/) which can be installed locally. You can also configure connection to a cloud based database service such as [Neo4j AuraDB](https://neo4j.com/product/auradb/).
You will need [Neo4j](https://github.com/neo4j/neo4j) available. We recommend the Neo4j Community 5 latest, available at [Neo4j Deploymnent Centre](https://neo4j.com/deployment-center/) which can be installed locally. You can also configure connection to a cloud based database service such as [Neo4j AuraDB](https://neo4j.com/product/auradb/).

## Tutorial

Expand Down
2 changes: 1 addition & 1 deletion diffo.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT
```elixir
Mix.install(
[
{:diffo, "~> 0.2.1"}
{:diffo, "~> 0.2.2"}
],
consolidate_protocols: false
)
Expand Down
1 change: 0 additions & 1 deletion lib/diffo/provider/assigner/assignment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,4 @@ defmodule Diffo.Provider.Assignment do
inspect(struct)
end
end

end
1 change: 0 additions & 1 deletion lib/diffo/provider/components/entity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,4 @@ defmodule Diffo.Provider.Entity do
preparations do
prepare build(sort: [id: :asc])
end

end
1 change: 0 additions & 1 deletion lib/diffo/provider/components/entity_ref.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,4 @@ defmodule Diffo.Provider.EntityRef do
preparations do
prepare build(load: [:entity], sort: [created_at: :desc])
end

end
1 change: 0 additions & 1 deletion lib/diffo/provider/components/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,4 @@ defmodule Diffo.Provider.Event do
Diffo.Util.to_iso8601(record.created_at)
)
end

end
1 change: 0 additions & 1 deletion lib/diffo/provider/components/external_identifier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,4 @@ defmodule Diffo.Provider.ExternalIdentifier do
preparations do
prepare build(load: [:owner], sort: [created_at: :desc])
end

end
1 change: 0 additions & 1 deletion lib/diffo/provider/components/feature.ex
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,4 @@ defmodule Diffo.Provider.Feature do
preparations do
prepare build(load: [:characteristics], sort: [name: :asc])
end

end
30 changes: 20 additions & 10 deletions lib/diffo/provider/components/instance/extension.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ defmodule Diffo.Provider.Instance.Extension do
schema: [
id: [
type: :string,
doc: "The id of the specification, a uuid4 the same in all environments, unique for name and major_version.",
doc:
"The id of the specification, a uuid4 the same in all environments, unique for name and major_version.",
required: true
],
name: [
Expand Down Expand Up @@ -114,7 +115,8 @@ defmodule Diffo.Provider.Instance.Extension do
required: true
],
value_type: [
doc: "The type of the characteristic's value. An atom module name such as an Ash.TypedStruct for a scalar value, or `{:array, module}` for an array of values of that type.",
doc:
"The type of the characteristic's value. An atom module name such as an Ash.TypedStruct for a scalar value, or `{:array, module}` for an array of values of that type.",
type: :any
]
]
Expand Down Expand Up @@ -182,11 +184,13 @@ defmodule Diffo.Provider.Instance.Extension do
required: true
],
party_type: [
doc: "The module of the Party kind. An atom module name such as a BaseParty-derived resource.",
doc:
"The module of the Party kind. An atom module name such as a BaseParty-derived resource.",
type: :any
],
reference: [
doc: "If true, no direct PartyRef edge is created; the party is reachable by graph traversal.",
doc:
"If true, no direct PartyRef edge is created; the party is reachable by graph traversal.",
type: :boolean,
default: false
],
Expand Down Expand Up @@ -215,7 +219,8 @@ defmodule Diffo.Provider.Instance.Extension do
@party_schema ++
[
constraints: [
doc: "Multiplicity constraints on the number of parties in this role, e.g. [min: 1, max: 3]",
doc:
"Multiplicity constraints on the number of parties in this role, e.g. [min: 1, max: 3]",
type: :keyword_list
]
]
Expand Down Expand Up @@ -247,7 +252,8 @@ defmodule Diffo.Provider.Instance.Extension do
type: :any
],
reference: [
doc: "If true, no direct PlaceRef edge is created; the place is reachable by graph traversal.",
doc:
"If true, no direct PlaceRef edge is created; the place is reachable by graph traversal.",
type: :boolean,
default: false
],
Expand Down Expand Up @@ -276,7 +282,8 @@ defmodule Diffo.Provider.Instance.Extension do
@place_schema ++
[
constraints: [
doc: "Multiplicity constraints on the number of places in this role, e.g. [min: 1, max: 3]",
doc:
"Multiplicity constraints on the number of places in this role, e.g. [min: 1, max: 3]",
type: :keyword_list
]
]
Expand All @@ -299,7 +306,8 @@ defmodule Diffo.Provider.Instance.Extension do

@structure %Spark.Dsl.Section{
name: :structure,
describe: "Defines the structural shape of the Instance — its specification, characteristics, features, parties, and places",
describe:
"Defines the structural shape of the Instance — its specification, characteristics, features, parties, and places",
examples: [
"""
structure do
Expand Down Expand Up @@ -330,7 +338,8 @@ defmodule Diffo.Provider.Instance.Extension do

@action_create %Spark.Dsl.Entity{
name: :create,
describe: "Marks a create action for instance build wiring, injecting :specified_by, :features, and :characteristics arguments",
describe:
"Marks a create action for instance build wiring, injecting :specified_by, :features, and :characteristics arguments",
target: Diffo.Provider.Instance.Extension.ActionCreate,
args: [:name],
schema: [
Expand Down Expand Up @@ -372,7 +381,8 @@ defmodule Diffo.Provider.Instance.Extension do

@behaviour_section %Spark.Dsl.Section{
name: :behaviour,
describe: "Defines the behavioural wiring for the Instance — actions, and in future triggers and tasks",
describe:
"Defines the behavioural wiring for the Instance — actions, and in future triggers and tasks",
examples: [
"""
behaviour do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ defmodule Diffo.Provider.Instance.Characteristic do
def relate_instance(result, changeset)
when is_struct(result) and is_struct(changeset, Ash.Changeset) do
characteristics = Ash.Changeset.get_argument(changeset, :characteristics)
Provider.relate_instance_characteristics(%Instance{id: result.id}, %{characteristics: characteristics})

Provider.relate_instance_characteristics(%Instance{id: result.id}, %{
characteristics: characteristics
})
end

@doc """
Expand Down
17 changes: 13 additions & 4 deletions lib/diffo/provider/components/instance/extension/party.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule Diffo.Provider.Instance.Party do
changeset
else
parties = Ash.Changeset.get_argument(changeset, :parties) || []

changeset
|> validate_roles(parties, declarations)
|> validate_constraints(parties, declarations)
Expand Down Expand Up @@ -55,15 +56,23 @@ defmodule Diffo.Provider.Instance.Party do

defp check_min(cs, _role, _count, nil), do: cs
defp check_min(cs, _role, count, min) when count >= min, do: cs

defp check_min(cs, role, count, min),
do: Ash.Changeset.add_error(cs, field: :parties,
message: "role #{inspect(role)} requires at least #{min} (got #{count})")
do:
Ash.Changeset.add_error(cs,
field: :parties,
message: "role #{inspect(role)} requires at least #{min} (got #{count})"
)

defp check_max(cs, _role, _count, nil), do: cs
defp check_max(cs, _role, count, max) when count <= max, do: cs

defp check_max(cs, role, count, max),
do: Ash.Changeset.add_error(cs, field: :parties,
message: "role #{inspect(role)} allows at most #{max} (got #{count})")
do:
Ash.Changeset.add_error(cs,
field: :parties,
message: "role #{inspect(role)} allows at most #{max} (got #{count})"
)

@doc """
Relates the parties in the changeset with the Extended Instance by creating party_ref
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ defmodule Diffo.Provider.Instance.Extension.PartyDeclaration do
@moduledoc """
PartyDeclaration - DSL entity declaring a party role on an Instance
"""
defstruct [:role, :party_type, :multiple, :reference, :calculate, :constraints,
__spark_metadata__: nil]
defstruct [
:role,
:party_type,
:multiple,
:reference,
:calculate,
:constraints,
__spark_metadata__: nil
]

defimpl String.Chars do
def to_string(struct), do: inspect(struct)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ defmodule Diffo.Provider.Instance.Extension.Persisters.PersistCharacteristics do
escaped = Macro.escape(declarations)
dsl_state = Transformer.persist(dsl_state, :characteristics, declarations)

{:ok, Transformer.eval(dsl_state, [], quote do
@doc false
def characteristics, do: unquote(escaped)
end)}
{:ok,
Transformer.eval(
dsl_state,
[],
quote do
@doc false
def characteristics, do: unquote(escaped)
end
)}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ defmodule Diffo.Provider.Instance.Extension.Persisters.PersistFeatures do
escaped = Macro.escape(declarations)
dsl_state = Transformer.persist(dsl_state, :features, declarations)

{:ok, Transformer.eval(dsl_state, [], quote do
@doc false
def features, do: unquote(escaped)
end)}
{:ok,
Transformer.eval(
dsl_state,
[],
quote do
@doc false
def features, do: unquote(escaped)
end
)}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ defmodule Diffo.Provider.Instance.Extension.Persisters.PersistParties do
escaped = Macro.escape(declarations)
dsl_state = Transformer.persist(dsl_state, :parties, declarations)

{:ok, Transformer.eval(dsl_state, [], quote do
@doc false
def parties, do: unquote(escaped)
end)}
{:ok,
Transformer.eval(
dsl_state,
[],
quote do
@doc false
def parties, do: unquote(escaped)
end
)}
end
end
Loading
Loading