Repository layout
Where each kind of thing lives in the Trax repository. For how to lay out an application built on Trax, see Structuring your application.
Before adding something to Trax, find an existing example of the same kind and mirror it: the same directory depth, the same naming, the same partial-class split.
Data models
A persisted model is a pair of files in two projects:
Trax.Effect/src/Trax.Effect/Models/<Entity>/<Entity>.cs
Trax.Effect/src/Trax.Effect.Data/Models/<Entity>/Persistent<Entity>.csThe base class carries the [Column] attributes, from
System.ComponentModel.DataAnnotations.Schema rather than from EF, which is what lets
Trax.Effect be referenced without EF Core. Persistent<Entity> inherits from it and adds a
static OnModelCreating that does the EF mapping through the Fluent API:
entity.ToTable("manifest", "trax"), the key, the index, the jsonb column types. [Table]
is not used in either tree.
Not everything under Models/ is a table. Models/Host/TraxHostInfo.cs is process identity
and Models/JunctionMetadata/ is in-flight junction state, and neither has a Persistent*
counterpart. A folder can also hold more than the entity: Models/Manifest/ carries
Manifest.cs, Exclusion.cs, IManifestProperties.cs and a DTOs/ folder. Put the entity,
the interface it satisfies and its DTOs together; do not split them across Models/.
Each provider has its own DbContext subclass at
Trax.Effect/src/Trax.Effect.Data.<Provider>/Services/<Provider>Context/<Provider>Context.cs,
sharing shape through the base DataContext<TDbContext> in Trax.Effect.Data. The providers
are Postgres, Sqlite and InMemory.
Migrations
Trax.Effect/src/Trax.Effect.Data.Postgres/Migrations/<NNN>_<name>.sql
Trax.Effect/src/Trax.Effect.Data.Sqlite/Migrations/<NNN>_<name>.sqlNumbered sequentially from 001 with no gaps, embedded by a csproj glob. The two sets are
numbered independently. Trax.Effect.Data.InMemory has no Migrations/ folder: the EF
in-memory provider stores objects rather than tables, so there is no DDL to run. See
Write a migration.
Builders
A partial class in its own directory, one file per feature area. See API conventions.
State machines
Trax.StateMachine/ holds the machines in every language, one directory each, with what spans
them at the top:
Trax.StateMachine/dotnet/ the C# engine: Trax.StateMachine.slnx, src/, tests/
Trax.StateMachine/ts/ @traxsharp/state-machine, the TypeScript engine and client
Trax.StateMachine/conformance/ the cases every engine answers, and the runner that sends them
Trax.StateMachine/docs/adr/ decisions that bind every languagedotnet/ is laid out like any other .NET folder. A case both engines must answer the same way
(a canonical-wire vector, a refusal code, a fixture) goes in conformance/, never in one
engine's tests. The tables a machine is stored in are models in Trax.Effect, as above.
Sample applications
Most samples are three or four projects under Trax.Samples/samples/<Topic>/:
Trax.Samples.<Name> the train library, with [TraxQuery] trains under Trains/
Trax.Samples.<Name>.Api the GraphQL host (or .Hub)
Trax.Samples.<Name>.Client the consumer (or .Scheduler, .Worker, .Runner)A sample that demonstrates one thing is one project: samples/Auth/ and
samples/SignalRBroadcaster/ are each a single web host with its trains under Trains/, because
neither has anything to say about a second process. Split when a second process has to exist
for the sample to make its point, not before. Bookworm goes the other way with five, because
cross-schema edges need a project of their own.
Resolvers and [ExtendObjectType] classes belong in a project, not in Program.cs. Which
project depends on what they extend: subscription extensions live in the train library
(Trax.Samples.ChatService/Subscriptions/), cross-schema edges in the project that owns the
join (Trax.Samples.Bookworm.CrossSchema/Edges/), and an extension over a type the host
defines lives in the host (Trax.Samples.Auth/Data/ArticleExtensions.cs).
Tests
See Test Conventions for the folder layout and the fixture patterns.
When you cannot find a pattern
Ask before improvising. A new pattern introduced without justification is almost always a missed pattern that already exists.