
* chore: add markdownlint-cli2 and config for mkdocs * style: enforce linting rules on mkdocs md files * chore: tweaks to markdownlint rules * style: linting changelog * style: linting release notes * style: linting .github md files * style: further linting of docs * style: linting readmes * chore: update linting script entries * docs: tweak release after rebase * chore: simplify root md linting config * chore: extend base config * chore: implement requested changes * chore: remove unnecessary exception * chore: fix comment type * styling: single config + list spacing * chore: implement requested changes * chore: use .cjs files for markdownlint config * chore: implement requested changes
2.6 KiB
Core building blocks
There are several core building blocks used in many places of the server. These are described here.
Handlers
A very important building block that gets reused in many places is the AsyncHandler
.
The idea is that a handler has 2 important functions.
canHandle
determines if this class is capable of correctly handling the request,
and throws an error if it can not.
For example, a class that converts JSON-LD to turtle can handle all requests containing JSON-LD data,
but does not know what to do with a request that contains a JPEG.
The second function is handle
where the class executes on the input data and returns the result.
If an error gets thrown here it means there is an issue with the input.
For example, if the input data claims to be JSON-LD but is actually not.
The power of using this interface really shines when using certain utility classes.
The one we use the most is the WaterfallHandler
,
which takes as input a list of handlers of the same type.
The input and output of a WaterfallHandler
is the same as those of its inputs,
meaning it can be used in the same places.
When doing a canHandle
call, it will iterate over all its input handlers
to find the first one where the canHandle
call succeeds,
and when calling handle
it will return the result of that specific handler.
This allows us to chain together many handlers that each have their specific niche,
such as handler that each support a specific HTTP method (GET/PUT/POST/etc.),
or handlers that only take requests targeting a specific subset of URLs.
To the parent class it will look like it has a handler that supports all methods,
while in practice it will be a WaterfallHandler
containing all these separate handlers.
Some other utility classes are the ParallelHandler
that runs all handlers simultaneously,
and the SequenceHandler
that runs all of them one after the other.
Since multiple handlers are executed here, these only work for handlers that have no output.
Streams
Almost all data is handled in a streaming fashion.
This allows us to work with very large resources without having to fully load them in memory,
a client could be reading data that is being returned by the server while the server is still reading the file.
Internally this means we are mostly handling data as Readable
objects.
We actually use Guarded<Readable>
which is an internal format we created to help us with error handling.
Such streams can be created using utility functions such as guardStream
and guardedStreamFrom
.
Similarly, we have a pipeSafely
to pipe streams in such a way that also helps with errors.