Context
An author builds schemas (inputs, outputs and views), refusals, errors, events, notification data and models in code. The ADRs that define those kinds did not fix one form for building them. 0078 and 0141 namedcreate() for domain events only, and
0095, 0140 and
0232 showed no form, or showed X.parse. The container vocabulary already uses
make for resolution, and 0294 decision 23 gives
each word one meaning, so make cannot also mean construction. The containers build the DI kinds, which gen wires from
constructor parameter types (0294 decision 17,
0263).
Decision
-
Value classes use
create(). Every class that an author builds in code is built with its staticX.create({...}). These classes are schemas (inputs, outputs and views), refusals and other errors, integration and domain events, notification data and models. -
create()is typed and validated.create()types its argument from the class it is called on, and it runs the generated validation. It returns a valid instance. -
Where it lives. The base classes provide
create():Schemain@stackra/validation;Refusaland the error classes in@stackra/errors; events in@stackra/events; notification data in@stackra/notifications; and models in@stackra/model. -
makeis not used. No value class is built with a method namedmake. In container vocabularymakemeans resolution, and 0294 decision 23 allows one meaning per word. -
Container classes are never constructed by an author. Actions, queries, services, listeners, drivers, policies,
interceptors and the other DI kinds are wired by gen from their constructor parameter types. An author writes neither
newnorcreate()for them. -
parseis the wire form, andcreate()is the code form.X.parse(input)is the form for untrusted input at the wire boundary, such as a request body or a message from outside the process. On invalid input it refuses with the invalid-argumentRefusal, which is theValidationErrorof 0095 and thevalidationkind of 0140.X.create({...})is the form for code, and it never reads untrusted input. On an invalid argument it is a programming error: it throws an invariant error of theinternalcategory of 0140, and it is never aRefusal. Each name has one meaning.
Consequences
- An author has one construction form for values made in code, and the validation runs when the value is made.
- A reader can tell from the call whether input came from outside (
parse) or from the code (create()). - A reviewer sees no
newof a service or an action in author code, because gen wires those. - A base class that does not yet provide
create()is a gap for its package to close. It is not a licence to usenew. - Tests may build values with factories, so the rule below does not reach them.
Enforcement
- planned — an oxlint house rule, under 0129, that refuses
newon a class whose base providescreate(). Tests may build values with factories instead. Nothing checks it today.