Marble.js v3 is right around the corner. Before its official release I would like to introduce the incoming new features and potential API breaking changes. This is a place for general discussion about all the changes, their relevance and potential impact to your codebase. Feel free to ask questions and propose improvements. 😊
General overview of new features:
- official support for TypeScript v3.7
- official support for RxJS v6.5
- official support for fp-ts v2.x
- simplified dependency injection via new
useContext
hook
- introducing new module
@marblejs/messaging
for building Microservices
- for MVP version with support for AMQP (RabbitMQ) and Redis transport layers
- I expect more transport layers to be introduced after release
- I'll post another issue when needed (or expect a surprise 😎🤪)
Incoming breaking changes::
Context API
[email protected]
brought a major breaking change in it's API (see changelog). It introduced changes that have a major impact to Context API (eg. Reader monad). What's new?
More explicit dependency binding. Previous API wasn't so precise, which could result to confusion, eg. when the dependency is lazily/eagerly evaluated.
Old way:
// eager
bindTo(WsServerToken)(websocketsServer.run),
// lazy
bindTo(WsServerToken)(websocketsServer),
New way:
// eager
bindEagerlyTo(WsServerToken)(websocketsServer),
// lazy
bindTo(WsServerToken)(websocketsServer),
bindLazilyTo(WsServerToken)(websocketsServer),
Reader creation:
Old way:
import { reader } from '@marblejs/core';
const someService = reader.map(ctx => {
// ...
});
New way:
import { reader } from '@marblejs/core';
import { pipe } from 'fp-ts/lib/pipeable';
import { map } from 'fp-ts/lib/Reader';
const someService = pipe(reader, map(ctx => {
// ...
}));
The release of fp-ts also had an impact to HTTP and WebSocket server creators. Since the run()
method on Reader, etc. has been replaced with a thunk, server creation also applied to this change. Bootstrap thunks are promisified, which means that they will return an instance only when started listening, if not then will throw an error.
Old way:
const server = createServer({
// ...
});
server.run();
New way:
const server = createServer({
// ...
});
await server();
Effect interface changes:
Currently Effect
interface defines three arguments where the second one is used for accessing contextual client, eg. HttpResponse, WebSocketClient, etc. Typicaly the second argument was not used very often. That's why in the next major version client parameter will be moved to context object which will result to reduced available number of parameters from 3 to 2:
Old way:
const foo$: WsEffect = (event$, client, meta) =>
event$.pipe(
matchEvent('FOO'),
// meta.ask --- context reader
);
New way:
const foo$: WsEffect = (event$, ctx) =>
event$.pipe(
matchEvent('FOO'),
// ctx.client --- contextual client
// ctx.ask --- context reader
);
This change also implies a much cleaner base Effect
interface:
interface Effect<I, O, Client> {
(input$: Observable<I>, ctx: EffectContext<Client>): Observable<O>;
}
interface EffectContext<T, U extends SchedulerLike = SchedulerLike> {
ask: ContextProvider;
scheduler: U;
client: T;
}
With that change the last argument of Effect interface is no more called as EffectMetadata
but rather as EffectContext
When dealing with error or output Effect, the developer had to use the attribute placed in the third effect argument, eg. const effect = (req$, client, { initiator, error }) => ...
In the case of ErrorEffect
the thrown error is passed to stream directly:
const error$: HttpErrorEffect<HttpError> = req$ =>
req$.pipe(
map(({ req, error }) => {
// ...
}),
);
In the case of OutputEffect
the message initiator (eg. initial request) is passed to stream directly:
const output$: HttpOutputEffect = out$ =>
res$.pipe(
map(({ req, res }) => {
// ...
}),
);
Nightly builds
If you would like to take part in beta testing feel free to try out canary
releases.
I'll try to inform here about new builds and changes that they introduce.
CC @tstelzer
enhancement help wanted question next