Migration from version 2.x
This chapter provides a set of guidelines to help you migrate from Marble.js version 2.x to the latest 3.x version.
The newest iteration comes with some API breaking change, but don’t worry, these are not game-changers, but rather convenient improvements that open the doors to new future possibilities. During the development process, the goal was to notify and discuss incoming changes within the community as early as possible. You can check here what has changed since the latest version.
[email protected]
is a required peer dependency (next to rxjs
)[email protected]
introduced changes that have a major impact to Context API (eg. Reader monad).Version 3.0 introduces more explicit dependency binding. Previous API wasn't so precise, which could result to confusion, eg. when the dependency is lazily/eagerly evaluated.
❌ Old:
import { bindTo } from '@marblejs/core';
// eager
bindTo(WsServerToken)(websocketsServer.run),
// lazy
bindTo(WsServerToken)(websocketsServer),
✅ New:
import { bindTo, bindLazilyTo, bindEagerlyTo } from '@marblejs/core';
// eager
bindEagerlyTo(WsServerToken)(websocketsServer),
// lazy
bindTo(WsServerToken)(websocketsServer),
bindLazilyTo(WsServerToken)(websocketsServer),
❌ Old:
import { reader } from '@marblejs/core';
const foo = reader.map(ctx => {
// ...
});
✅ New:
You can create context readers via raw Reader monad composition (using available fp-ts toolset) or using
createReader
utility function that saves a lot of unnecessary boilerplate.import { createReader, reader } from '@marblejs/core';
import { pipe } from 'fp-ts/lib/pipeable';
import { map } from 'fp-ts/lib/Reader';
const foo1 = pipe(reader, map(ctx => {
// ...
}));
// or much simpler
const foo2 = createReader(ctx => {
// ...
});
The release of fp-ts also had an impact to HTTP and WebSocket server creators.
run()
method on Reader, etc. has been replaced with IO thunk. Additionally all server creators are promisified, which means that they will return an instance only when started listening. The change applies to all main modules: @marblejs/core
, @marblejs/websockets
, @marblejs/messaging
. More info you can find in PR #198Bootstrapping:
❌ Old:
const server = createServer({
// ...
});
server.run();
✅ New:
const server = createServer({
// ...
});
await (await server)();
Unified config interfaces for all kind of server creators:
websockets
import { createWebSocketServer, webSocketListener } from '@marblejs/websockets';
const webSocketServer = createWebSocketServer({
// ...
listener: webSocketListener({
middlewares: [...],
effects: [...],
}),
});
http
import { createServer, httpListener } from '@marblejs/core';
const server = createServer({
// ...
listener: httpListener({
middlewares: [...],
effects: [...],
}),
});
messaging
import { createMicroservice, messagingListener } from '@marblejs/messaging';
const microservice = createMicroservice({
// ...
listener: messagingListener({
middlewares: [...],
effects: [...],
}),
});
Marble.js v2.0
Effect
interface defines three arguments where the second one is used for accessing contextual client, eg. HttpResponse, WebSocketClient, etc. Typically the second argument was not used very often. That's why in the next major version client parameter moves to context object. It results in reduced available number of parameters from 3 to 2.The change applies to all Effect interfaces, eg.
HttpEffect
, WsEffect
, MsgEffect
❌ Old:
const foo$: WsEffect = (event$, client, meta) =>
event$.pipe(
matchEvent('FOO'),
// meta.ask --- context reader
);
✅ New:
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. In Marble.js v3.0 the thrown error is passed to stream directly.
❌ Old:
const error$: HttpErrorEffect<HttpError> = (req$, client, { error }) =>
req$.pipe(
map(req => {
// ...
}),
);
✅ New:
const error$: HttpErrorEffect<HttpError> = req$ =>
req$.pipe(
map(({ req, error }) => {
// ...
}),
);
❌ Old:
const output$: HttpOutputEffect = (res$, client, { initiator }) =>
res$.pipe(
map(res => {
// ...
}),
);
✅ New:
const output$: HttpOutputEffect = res$ =>
res$.pipe(
map(({ req, res }) => {
// ...
}),
);
The HttpResponse object is no more carried separately but together with correlated HttpRequest.
interface HttpRequest {
url: string;
method: HttpMethod;
body: Body;
params: Params;
query: Query;
response: HttpResponse; // 👈
}
const effect$ = r.pipe(
r.matchPath('/'),
r.matchType('GET'),
r.useEffect((req$, ctx) => req$.pipe(
map(req => ...),
// req.response -- HttpResponse object
// ctx.client -- HttpServer object
)));
The newest implementation of logger middleware uses underneath the pluggable Logger dependency, so dedicated log streaming is unnecessary. The developer can simply override default Logger binding and forward the incoming logs on his own. Also, since
HttpRequest
contains the response object attached to it - res
attribute is redundant.❌ Old:
interface LoggerOptions {
silent?: boolean;
stream?: WritableLike;
filter?: (res: HttpResponse, req: HttpRequest) => boolean;
}
✅ New:
interface LoggerOptions {
silent?: boolean;
filter?: (req: HttpRequest) => boolean;
}
Last modified 3yr ago