# Changelog

All notable changes to ActiveMemory are documented here. Versions follow
[Semantic Versioning](https://semver.org); while the package is pre-1.0 a minor
bump may carry a behavior change, and those are called out below.

## 0.8.0

Ecto compatibility. A `Table` can now be typed or defined as an Ecto schema, and
the read API covers what `Ecto.Repo` offers.

### Added

- `field/3` in an `attributes` block takes an optional Ecto type —
  `field :name, :string` — defaulting to `:any`. Types are not enforced on write;
  they power `Ecto.Changeset` casting and validation. Available as
  `__attributes__(:types)`.
- A `Table` can skip `attributes` entirely and define an Ecto `embedded_schema`.
  All table metadata is derived from the schema, so the module is a real Ecto
  schema and every changeset function works on it.
- Autogenerated schema fields are honored on write: an `:id`/`:binary_id` primary
  key, a custom key such as `@primary_key {:uuid, Ecto.UUID, autogenerate: true}`,
  and `timestamps()`.
- `write/1` accepts an `Ecto.Changeset` as well as a struct, like
  `c:Ecto.Repo.insert/2`. An invalid changeset returns `{:error, changeset}` with its
  `action` set to `:insert`, which is what a Phoenix form needs to render errors.
- `get/1`, `get!/1`, `get_by/1`, `get_by!/1`, `one!/1`, `reload/1` and `reload!/1`.
  The bang variants raise `ActiveMemory.NotFoundError`.
- `count/1`, which asks the table for its size rather than copying records out, so
  it is O(1). On a `ttl` table it takes `sweep: true` to exclude expired but
  unswept records.
- `exists?/2`.
- `:order_by`, `:limit` and `:offset` on `all/1` and `select/2`. Sorting happens
  after reading, and uses a value's own `compare/2` when it has one, so `Decimal`
  and the calendar types order correctly rather than by Erlang term order.
- `ActiveMemory.NotFoundError` and `ActiveMemory.MultipleResultsError`.
- `__attributes__(:primary_key)`, the table's first field — what ETS and Mnesia
  key a record on.

### Changed

- **`one/1` now raises `ActiveMemory.MultipleResultsError`** when a query matches
  more than one record, instead of returning `{:error, :more_than_one_result}`.
  This matches `c:Ecto.Repo.one/2`. `withdraw/1` raises there too, since deleting an
  arbitrary one of several matches would be wrong. Callers matching on
  `{:error, :more_than_one_result}` need updating.
- An Ecto schema table that declares a primary key somewhere other than its first
  field, or a composite primary key, raises when the table is created. An in memory
  table keys a record on its first field, so neither can be honored, and the
  previous behavior would have read the wrong field.

### Fixed

- An Ecto schema table with an autogenerating primary key of a custom type
  (`@primary_key {:uuid, Ecto.UUID, autogenerate: true}`) generated no key, so
  every record was written under a `nil` key and collapsed onto one entry.
- Reconciling a recovered Mnesia table's options could crash the store's startup:
  an option change Mnesia refused raised out of `init/1`, and a table naming more
  than one copy type (`ram_copies` plus `disc_copies`, for instance) crashed the
  reconciliation even when the configuration was valid. A refused change is now
  logged as a warning and skipped, so the store starts and the table keeps its
  current setting. A copy configuration that is wrong in the code itself — the same
  node under two copy types — raises `ArgumentError`, since it would fail on every
  boot.
- Compile warnings from the test dependency `local_cluster` (deprecated `:slave`
  usage, charlist syntax) are gone with the upgrade to 2.1.

### Documentation

- A "Deleting a record" section explaining that `delete/1` matches a record in
  full, that a stale struct therefore removes nothing while still returning `:ok`,
  and that `withdraw/1` is the query based alternative.
- A `ttl` on an Ecto schema table needs an explicit `field :expires_at, :integer`;
  the `attributes` DSL still adds it for you.
- `Store` and `ActiveRepo` documentation now describes the same behavior for the
  operations they share.
- A "Testing" section covering which test modules can run `async: true` — a module
  owning its own table and store runs concurrently today — and which need
  `async: false`, namely modules sharing the application's store, since a table's
  module name is its physical table name.
- The `majority` option, Mnesia's quorum writes, is documented as the mitigation for
  partition behavior rather than one line in a list of options.

## 0.7.4

### Fixed

- `use ActiveMemory.Store` and `use ActiveMemory.ActiveRepo` read the table's `ttl`
  while the caller's module compiled, making every table a compile time dependency
  of its store. Tooling that compiles a store's file without the table module
  loaded — a language server, for one — raised `UndefinedFunctionError` on code
  that compiles and runs correctly. The lookup happens at runtime now.

## 0.7.3

### Fixed

- Removed an unreachable `ttl` clause that newer Elixir type checkers flagged in
  any project defining a store on a table without a `ttl`.
- Removed unreachable clauses in the ETS adapter's `select/2`.

## 0.7.2

### Changed

- `delete_all/0` returns `:ok` on success. The ETS adapter had returned the raw
  `true` from `:ets.delete_all_objects/1` and the Mnesia adapter `{:atomic, :ok}`,
  neither matching the documented `:ok | {:error, any()}` spec, so
  `:ok = Store.delete_all()` raised a `MatchError`.

## 0.7.1 and earlier

See the [commit history](https://github.com/SullysMustyRuby/active_memory/commits/main)
for releases before this changelog was kept. Notable additions were record expiry
(`ttl`), `ActiveMemory.ActiveRepo` for managing several tables from one process,
`ActiveMemory.TableHeir` so an ETS table survives its store crashing, and making
`withdraw/1` a single atomic operation.
