Skip to content

Atlas 0.3.0

Released 2026-10-12 · v0.3.0 on GitHub · install with "atlas": "github:AndreyDAraya/atlas#v0.3.0"

Safe cascade: a fact cycle only data shows no longer runs a worker until the process dies, a failed query stays a query (no exception, no delivery budget), and the shop example consumes every fact it emits.

  • StoredFact gained depth; the commit input facts accept it. depth is the length of an entry’s cause chain: 0 for what a dispatched command or a released timer writes, the cause’s depth + 1 for what a delivery writes. A commit’s facts carry it as an optional field (absent: 0); factsAfter, causedBy and commit return it as a number, and the runtime treats a missing one as 0. The Postgres adapter adds the column itself: migrate() runs schema.sql, which now carries ALTER TABLE atlas_facts ADD COLUMN IF NOT EXISTS depth int NOT NULL DEFAULT 0, so a boot that migrates adds it once and rows from before it read as depth: 0; nothing is rewritten. Migration: custom Store adapters persist depth from the commit’s facts and return it on every fact they read back (a fact committed without it reads back as depth: 0); the contract suite in test/store-contract.ts exercises it. (d4b7eaa)
  • New delivery outcome: failed with code cascade-exceeded. A delivery of an entry whose depth is past maxDepth (below) settles like a poisoned one: a failed trace event with code cascade-exceeded (the reason names the type, its depth and maxDepth), the consumer’s cursor advances, nothing is written, nothing is retried; the entry’s cause chain stays in the outbox for the operator. It is a delivery code only: a dispatch or a query never answers it. Migration: observers and dashboards that enumerate the framework codes add cascade-exceeded next to store-rejected; one in the trace means a fact cycle atlas check ATLAS011 could not see in the declarations, or a paged fan-out of more than maxDepth pages. (d4b7eaa)
  • createRuntime({ maxDepth }) (default 1000): the deepest cause chain a delivery accepts, the fuse for a cascade that never ends in start() workers, where maxSteps (a budget per drain() call) means nothing. The trade-off: a paged fan-out counts one level per page, so a fan-out of more than maxDepth pages needs a larger maxDepth (1000 pages of 1000 rows is a million rows per fact). drain() keeps maxSteps for tests and CLIs. (d4b7eaa)
  • Shop example: order-summary keeps a customers view (userId, name, ordersPlaced) projected from UserRegistered and OrderPlaced, with an admin-only Customers view query ordered by name. (d091cbb)
  • A data-dependent fact cycle (one ATLAS011 cannot see in the declarations) delivered by start() ran until the process died: only drain() bounded a cascade, with maxSteps, and a worker runs forever. Deliveries are now bounded by maxDepth. (d4b7eaa)
  • A failed query (store-failed, upcast-failed, query-failed) was recorded as a delivery step: an observe callback that threw turned the query’s rejection into an exception out of query(), and each failure during a drain() spent one of its maxSteps. Failed queries are traced and observed outside delivery: the observer’s throw is swallowed and no budget is spent. (3bb9e46)
  • Shop example: UserRegistered had no consumer (atlas check ATLAS010). (d091cbb)

From CHANGELOG.md. All versions: Releases.