← Back to the projects

entifix

A TypeScript framework for entity-driven applications on Effect, from metadata to repositories to React controls.

Overview

entifix is an entity framework for TypeScript: an entity is described once, and the same description drives the repository that stores it, the route that serves it, the table that lists it and the form that edits it.

It was extracted from a marketplace that had driven it for a year, so its code is exercised; what is new is the packaging into independent tiers, each adoptable without the ones above it.

Patterns

  • Entities described once

    An entity's metadata — its members, types, links and what is filterable — drives its repository, its routes, its tables and its forms.

  • Built on Effect

    Use cases, repositories and services are Effect programs, so errors, dependencies and resources are typed rather than hoped for.

  • A tier contract

    Six tiers, from standalone to testing. A package depends only on its own tier or below, and a check fails the build when an edge breaks the rule.

  • A seam the host configures

    The framework reads no file: catalogs, grants and token checks cross into it as values, so an application decides them and the framework stays generic.

  • Examples as proof

    Each example takes a different cut through the tiers, which proves a tier can be adopted without the ones above it.

File structure

  • packages/
    • ts/Framework-agnostic packages: core, business, the adapters and the testing kits.
      • core/Entities, metadata, links and the load use case: tier 1.
    • react/React controls and the integration that wires them to entities.
    • next/The Next.js shell and its i18n.
    • effect/
      • service-shell/The Effect service shell that serves entities over HTTP.
    • style/Design tokens: tier 0, depends on nothing.
  • examples/Minimal, service and workspace examples, each with its own e2e.
  • tools/
    • tiers/The tier contract, checked against the tree.
  • docs/
    • adr/The decision records.

Architecture decisions

Every significant decision is recorded, with the symptom that should send a reader — or an agent — to it before the rule is broken.

The records are written in English.

How the records steer the work

People and agents read the same records. Each one names the symptom that should send a reader to it, so a rule is found before it is broken, not after.

  1. 1DecideA choice that would be costly to undo, or easy to break by accident, gets a numbered record.
  2. 2RecordIts header holds a status, a date, an area and a Read when line: the symptom that should bring a reader back.
  3. 3PointThe repository’s CLAUDE.md sends every agent session to docs/adr, whose README indexes the records.
  4. 4MatchWhen a task meets a symptom — a failing check, a strange build — the agent finds the record whose Read when names it, and follows its rule.
  5. 5EvolveA fact that changes is corrected in place, on a Revised line. A decision that no longer holds gets a new record that supersedes it. Nothing is deleted.

The records below are copied from each repository by a script, and this repository’s CI fails when a copy drifts from its record.

Anatomy of a record: the header of ADR 0004, the newest with a Read when line.
  1. # 4. The examples are the composability proof, and one of them gates every pull requestA number that never changes, and the decision in one line. The README lists them.
  2. - Status: AcceptedWhere it stands. A superseded record stays, and names the record that replaced it.
  3. - Date: 2026-09-16When it was decided. A later correction adds a Revised line below, with its own date.
  4. - Area: testingThe part of the system it governs.
  5. - Read when: adding an example, adding an e2e journey, putting an entity class inside a Next application, or wondering why a verb declared with `@useCase()` never appears on screen — entity classes need SWC's 2022-03 decorators, which Next's own compiler cannot provideWhat an agent matches against: the symptom it would meet, not the topic.
  1. 0001The tier contract, and the seam a host configures acrossAccepted

    0001The tier contract, and the seam a host configures across

    Read whenadding a package, adding a dependency between two, deciding whether something is public API, or about to bake a host's value into framework code — a downward edge can still be illegal, and the seam takes values rather than paths

    The decision

    • i18n is T2, not T0.
    • testing-* is T5, above everything.
    • A downward edge can still be illegal.
    • The subpath is not decoration — it is the part that works.
    • An exemption is named one at a time, with its reason on the line.

    Acceptedplatform

    Read the record →
  2. 0002entifix is MIT, and the trigger that would freeze that choiceAccepted
  3. 0003Releasing without a credential, and the version a commit is allowed to cutAccepted
  4. 0004The examples are the composability proof, and one of them gates every pull requestAccepted