Presentation
A thin save method maps the wire input, calls the use case, and maps ValidationFailure to a 400.
The endpoint lives at lib/src/presentation/notes/. The wire mapper does not. It lives at lib/src/presentation/mappers/wire_mappers.dart, shared with every other endpoint. Adding a second feature means a new function in that file, not a new mapper folder.
SaveNoteInput and NoteDto are the wire model. The client sends a title and a body. The response adds id and createdAt. Those classes are what .spy.yml inputs and DTOs become after serverpod generate. This package writes them by hand so the tests do not need a generated protocol.
NotesEndpoint.save is the whole edge:
Future<NoteDto> save(SaveNoteInput input) async {
try {
final note = await useCases.saveNote().execute(
title: input.title,
body: input.body,
);
return mappers.toNoteDto(note);
} on ValidationFailure catch (error) {
throw NoteRequestFailure(400, error.message);
}
}On a generated server this method sits on a class that extends Endpoint, and the first argument is a Session. The body stays map, call, map. WireMappers turns a Note into a NoteDto. The endpoint does not construct NoteRepositoryImpl. It receives UseCases from the composition root.
list is the same shape for the read path. The endpoint is lib/src/presentation/notes/notes_endpoint.dart. The mapper is lib/src/presentation/mappers/wire_mappers.dart.
A blank title becomes status 400. A store failure is not caught here and is not relabeled as ValidationFailure.
Next: Composition root.