Skip to main content

Getting Started

4 min read

Changelog

What changed in each release, and what you need to do about it.

Core and the framework adapters share one version. @directive-run/query, el, mcp, lint, timeline, optimistic, mutator, and scaffold version independently.

Full per-package detail lives in each package's CHANGELOG.md on npm and GitHub. This page carries the changes that affect how you write code.


1.27.0

Upgrading from 1.26.x or earlier

Nothing requires a source edit. Two results grew, and both are worth a look if you act on them programmatically — see Tag inheritance below.

Tags travel down the derivation graph

system.meta.byTag("pii") now also reports derivations that read a tagged fact, so a computed value carrying PII no longer reads as untagged:

system.meta.byTag("pii");
// [ { type: "fact",       id: "email"  },
//   { type: "derivation", id: "domain", via: "inherited" } ]

MetaMatch gained via, which is "inherited" on a derivation matched this way and absent when the tag was authored on the definition itself. system.meta.derivation(id) exposes what a derivation picked up as inheritedTags, and meta: { inheritsTags: false } marks a derivation where the claim stops holding.

Two results grew. Anything acting on byTag() — a redactor, an audit filter, a compliance sweep — now covers derivations it did not before. And system.meta.derivation(id) returns an object where it previously returned undefined for a derivation with no authored meta, so if (system.meta.derivation(id)) no longer means "did anyone annotate this". Compare against ?.label or ?.tags instead.

See Definition Meta.

An effect reads its own writes through derived

Write a fact and read a derivation of it later in the same effect body, and you now get the value that follows from the write:

run: (facts, prev, derived) => {
  facts.quantity = 5;
  derived.subtotal;   // reflects quantity === 5
}

facts always read back immediately; derived did not, because an effect body runs inside a batch and invalidation waited for the flush. A constraint's when() is not batched, so the identical two lines already worked there. Invalidation is now eager per write and only the notification still waits, so listeners fire at exactly the moment they did before.

See Effects → Reading your own writes.

Reaching through system.derive from inside a module warns

system.derive is the single-module accessor. In a createSystem({ modules }) system it holds module names, so the same read returns undefined, the gate goes falsy, and the constraint silently never fires. Development builds now warn, naming the module that owns the derivation and both correct routes.

Production builds are unaffected, and the warning fires once per name per system.

Smaller

  • settle() no longer resolves while a derivation invalidation is still undelivered.
  • system.inspect() gained pendingInvalidations and observedDerivations.
  • createModule rejects a fact key or derivation ID containing U+001F, which collided with the separator used internally to namespace dependency entries. No identifier written in normal source contains it.

1.26.0

Constraints and effects receive derived

A constraint's when() is now (facts, derived) and an effect's run() is now (facts, prev, derived). Derivation bodies have always been (facts, derived); these now match.

constraints: {
  offerShipping: {
    when: (facts, derived) =>
      facts.checkoutOpen && derived.qualifiesForFreeShipping,
    require: { type: "OFFER_FREE_SHIPPING" },
  },
},

The parameter is additive — a callback that ignores it behaves exactly as before, so no existing code needs to change. What it replaces is reaching back through system.derive from inside a module, which breaks silently once that module is composed into a multi-module system.

A read through derived registers a dependency on the auto-tracked path: a synchronous body, no explicit deps, reading before any await. It does not in three cases — deps declared, async: true on a constraint, or a read after an await.

An explicit deps entry may now name a derivation. It could not before in either direction: the runtime matched deps only against fact keys, and the type refused the correct code anyway.

A derivation dependency wakes on possible movement; a fact dependency wakes on a change. Writing a fact its current value is not a change and does not run the effect. A derivation has no value to compare at the moment its inputs move, so it wakes its dependent whenever the facts underneath it move — whether or not the derived value moved with them.

An effect whose deps name only derivations does not run at startup, where one naming a fact does. Startup announces the fact keys init wrote, and a derivation is not among them. If the effect establishes something that must exist from the start, name a fact it reads as well, or do the setup in init.

See Constraints and Effects.


Earlier releases

For releases before 1.26.0, see the per-package changelogs on GitHub:

Previous
Comparison

Stay in the loop. Sign up for our newsletter.

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works

Directive - Constraint-Driven Runtime for TypeScript | AI Guardrails & State Management