Network.export() only writes the "type" key if it's a ChanType.QUERY;
so the config on disk has no "type".
This causes it to be undefined when loading, which breaks various other
checks, and then drops it the next time the config is saved.
Keep happy path on the left and try to return as early
as we can to help the reader understand the logic better
The function is too large to be able to quickly scan an if / else
chain and see the function return at the end
Client and ClientManager deal with both 'dehydrated' channels/networks (ie. directly
from JSON configuration) and the 'rehydrated' ones (classes, with socket objects,
message arrays, etc.).
However, because their attributes are similar, both types were used interchangeably,
which becomes an issue when splitting Client's configuration loading into smaller
methods.
Prior to this, the search is still racy but one tends to notice
this only when the DB is large or network is involved.
The user can initiate a search, get bored, navigate to another chan
issue a different search.
Now however, the results of the first search come back in and
hilarity ensues as we are now confused with the state.
To avoid this, keep track of the last search done and any result
that comes in that isn't equal to the active query is garbage and
can be dropped.
The only reason we accepted a client was that so we have access
to the next message id when we need it.
So let's accept an id provider function instead.
The interface should not contain things that aren't the API of the
storage interface.
Further, rename ISqliteMessageStorage to SearchableMessageStorage,
as that's also an implementation detail.
We'll never have a second sqlite backend, so the name seems
strange.
TL is stupid and doesn't wait for message{Provider,Storage} to
settle before it starts using the store.
While this should be fixed globally, we can hack around the problem
by pushing everything onto the call stack and hope that we'll eventually
finish the setup before we blow the stack.
TS type assertions need to be avoided.
The following trivial example demonstrates why
```
type Person = {
name: string;
isBad: boolean;
};
function makePerson(): Person {
const p: Person = {name: 'whatever'} as Person
p.isBad = false
return p // theoretically we are now good, p is a Person
}
```
Should the type ever change though, TS will happily trot along
```
type Person = {
name: string;
isBad: boolean;
omgHowCouldYou: number;
};
function makePerson(): Person {
const p: Person = {name: 'whatever'} as Person
p.isBad = true
return p // p is *not* a Person, omgHowCouldYou is missing
}
```
But we pinky swore to the compiler that p is in fact a Person.
In other words, the types are now wrong and you will fail during
runtime.
Message stores are more complicated that a sync "fire and forget"
API allows for.
For starters, non trivial stores (say sqlite) can fail during init
and we want to be able to catch that.
Second, we really need to be able to run migrations and such, which
may block (and fail) the activation of the store.
On the plus side, this pushes error handling to the caller rather
than the stores, which is a good thing as that allows us to eventually
push this to the client in the UI, rather than just logging it in the
server on stdout