Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
reafactor(core, websockets): context (#106)
* feat(@integration): added example usage of WebSocket middleware (logger$) * imp(core): naming correction: injector -> context * feat(core): new context implementation * style(@integration): WebSocketServerToken -> WsServerToken * style(core, websockets): naming coorection: Injectable -> ContextReader * style(core, websockets): naming correction: askContext -> reader * fix(core): corrected the way how the dependencies inside the context are resolved * fix(core, @integration): context: bind eagerly readers * fix(core, websockets): improved the way how the server is bootsrapped + rermoved http server from the context container * feat(core): createServer.run with predicate prameter
- Loading branch information
1 parent
df5abff
commit 1963b61
Showing
41 changed files
with
472 additions
and
380 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,39 @@ | ||
import { createServer, matchEvent, ServerEvent, HttpServerEffect, bind } from '@marblejs/core'; | ||
import { createServer, matchEvent, ServerEvent, HttpServerEffect, bindTo } from '@marblejs/core'; | ||
import { mapToServer } from '@marblejs/websockets'; | ||
import { merge } from 'rxjs'; | ||
import { tap, map } from 'rxjs/operators'; | ||
import { httpServer } from './http.listener'; | ||
import { webSocketServer } from './ws.listener'; | ||
import { WebSocketServerToken } from './tokens'; | ||
import { WsServerToken } from './tokens'; | ||
import httpListener from './http.listener'; | ||
import webSocketListener from './ws.listener'; | ||
|
||
const upgrade$: HttpServerEffect = (event$, _, { inject }) => | ||
const upgrade$: HttpServerEffect = (event$, _, { ask }) => | ||
event$.pipe( | ||
matchEvent(ServerEvent.upgrade), | ||
mapToServer({ | ||
path: '/api/:version/ws', | ||
server: inject(WebSocketServerToken), | ||
server: ask(WsServerToken), | ||
}), | ||
); | ||
|
||
const listen$: HttpServerEffect = event$ => | ||
const listening$: HttpServerEffect = event$ => | ||
event$.pipe( | ||
matchEvent(ServerEvent.listen), | ||
matchEvent(ServerEvent.listening), | ||
map(event => event.payload), | ||
tap(({ port, host }) => console.log(`Server running @ http://${host}:${port}/ 🚀`)), | ||
); | ||
|
||
export const server = createServer({ | ||
hostname: '127.0.0.1', | ||
port: 1337, | ||
httpListener: httpServer, | ||
httpListener, | ||
dependencies: [ | ||
bind(WebSocketServerToken).to(webSocketServer({ noServer: true })), | ||
bindTo(WsServerToken)(webSocketListener().run), | ||
], | ||
event$: (...args) => merge( | ||
listen$(...args), | ||
listening$(...args), | ||
upgrade$(...args), | ||
), | ||
}); | ||
|
||
server.run( | ||
process.env.NODE_ENV !== 'test' | ||
); |
7 changes: 7 additions & 0 deletions
7
packages/@integration/src/middlewares/logger.ws-middleware.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { WsMiddlewareEffect } from '@marblejs/websockets'; | ||
import { tap } from 'rxjs/operators'; | ||
|
||
export const logger$: WsMiddlewareEffect = event$ => | ||
event$.pipe( | ||
tap(e => console.log(`type: ${e.type}, payload: ${e.payload}`)), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { createInjectionToken } from '@marblejs/core'; | ||
import { createContextToken } from '@marblejs/core'; | ||
import { MarbleWebSocketServer } from '@marblejs/websockets'; | ||
|
||
export const WebSocketServerToken = createInjectionToken<MarbleWebSocketServer>(); | ||
export const WsServerToken = createContextToken<MarbleWebSocketServer>(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import * as R from 'fp-ts/lib/Reader'; | ||
import * as M from 'fp-ts/lib/Map'; | ||
import { Setoid } from 'fp-ts/lib/Setoid'; | ||
import { Option } from 'fp-ts/lib/Option'; | ||
import { contramap, ordString, Ord } from 'fp-ts/lib/Ord'; | ||
import { ContextToken } from './context.token.factory'; | ||
|
||
const ordContextToken: Ord<ContextToken<any>> = contramap((t: ContextToken) => t._id, ordString); | ||
const setoidContextToken: Setoid<ContextToken> = { equals: ordContextToken.equals }; | ||
|
||
export interface Context extends Map<ContextToken, R.Reader<any, any> | any> {} | ||
|
||
export interface ContextProvider { <T>(token: ContextToken<T>): Option<T>; } | ||
|
||
export interface ContextReader extends R.Reader<Context, any> {} | ||
|
||
export interface ContextEagerReader { (ctx: Context): any; } | ||
|
||
export type ContextDependency = ContextReader | ContextEagerReader; | ||
|
||
export interface BoundDependency<T, U extends ContextDependency = ContextDependency> { | ||
token: ContextToken<T>; | ||
dependency: U; | ||
} | ||
|
||
const isReader = (x: any): x is R.Reader<any, any> => !!x.run; | ||
|
||
export const createContext = () => M.empty; | ||
|
||
export const register = <T>(boundDependency: BoundDependency<T, any>) => (context: Context) => | ||
M.insert(setoidContextToken)( | ||
boundDependency.token, | ||
isReader(boundDependency.dependency) | ||
? boundDependency.dependency | ||
: boundDependency.dependency(context), | ||
context | ||
); | ||
|
||
export const registerAll = (boundDependencies: BoundDependency<any, any>[]) => (context: Context) => | ||
boundDependencies.reduce( | ||
(ctx, dependency) => register(dependency)(ctx), | ||
context, | ||
); | ||
|
||
export const lookup = (context: Context) => <T>(token: ContextToken<T>): Option<T> => | ||
M.lookup(ordContextToken)(token, context).map(dependency => | ||
isReader(dependency) | ||
? dependency.run(context) | ||
: dependency | ||
); | ||
|
||
export const bindTo = | ||
<T>(token: ContextToken<T>) => | ||
<U extends ContextDependency>(dependency: U): BoundDependency<T, U> => | ||
({ token, dependency }); | ||
|
||
export const reader = R.ask<Context>().map(lookup); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import * as uuid from 'uuid'; | ||
|
||
export class ContextToken<T = any> { | ||
_id = uuid(); | ||
_T!: T; | ||
} | ||
|
||
export const createContextToken = <T>() => | ||
new class extends ContextToken<T> {}; |
138 changes: 138 additions & 0 deletions
138
packages/core/src/context/specs/context.factory.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import { isEmpty, size } from 'fp-ts/lib/Map'; | ||
import { some } from 'fp-ts/lib/Option'; | ||
import { ask } from 'fp-ts/lib/Reader'; | ||
import { | ||
Context, | ||
ContextEagerReader, | ||
createContext, | ||
bindTo, | ||
register, | ||
registerAll, | ||
reader, | ||
lookup, | ||
} from '../context.factory'; | ||
import { createContextToken } from '../context.token.factory'; | ||
|
||
describe('#bindTo', () => { | ||
test('binds reader to token', () => { | ||
// given | ||
const context = createContext(); | ||
const reader = ask<Context>().map(_ => 'test'); | ||
const Token = createContextToken<typeof reader>(); | ||
|
||
// when | ||
const boundDependency = bindTo(Token)(reader); | ||
|
||
// then | ||
expect(boundDependency.token).toBe(Token); | ||
expect((boundDependency.dependency).run(context)).toEqual('test'); | ||
}); | ||
|
||
test('binds singleton to token', () => { | ||
// given | ||
const singleton: ContextEagerReader = _ => 'test'; | ||
const Token = createContextToken<typeof reader>(); | ||
|
||
// when | ||
const boundDependency = bindTo(Token)(singleton); | ||
|
||
// then | ||
expect(boundDependency.token).toBe(Token); | ||
expect(boundDependency.dependency).toEqual(singleton); | ||
}); | ||
}); | ||
|
||
describe('#createContext', () => { | ||
test('creates empty context', () => { | ||
const context = createContext(); | ||
expect(isEmpty(context)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('#register', () => { | ||
test('registers bound readers', () => { | ||
// given | ||
const token = createContextToken(); | ||
const dependency = ask<Context>().map(_ => 'test'); | ||
const boundDependency = bindTo(token)(dependency); | ||
|
||
// when | ||
const context = register(boundDependency)(createContext()); | ||
|
||
// then | ||
expect(lookup(context)(token)).toEqual(some('test')); | ||
}); | ||
}); | ||
|
||
describe('#registerAll', () => { | ||
test('registers set of bound readers', () => { | ||
// given | ||
const token1 = createContextToken<string>(); | ||
const token2 = createContextToken<string>(); | ||
const token3 = createContextToken<string>(); | ||
const dependency1 = ask<Context>().map(_ => 'test_1'); | ||
const dependency2 = ask<Context>().map(_ => 'test_2'); | ||
const dependency3 = ask<Context>().map(_ => 'test_3'); | ||
|
||
// when | ||
const context = registerAll([ | ||
bindTo(token1)(dependency1), | ||
bindTo(token2)(dependency2), | ||
bindTo(token3)(dependency3), | ||
])(createContext()); | ||
|
||
// then | ||
expect(size(context)).toEqual(3); | ||
expect(lookup(context)(token2)).toEqual(some('test_2')); | ||
}); | ||
}); | ||
|
||
describe('#reader', () => { | ||
test('asks context for a reader dependency', () => { | ||
// given | ||
const token1 = createContextToken<string>(); | ||
const token2 = createContextToken<string>(); | ||
const dependency1 = reader.map(() => 'test_1'); | ||
const dependency2 = reader | ||
.map(ask => ask(token1) | ||
.map(v => v + '_2') | ||
.getOrElse('')); | ||
|
||
// when ordered | ||
const context1 = registerAll([ | ||
bindTo(token1)(dependency1), | ||
bindTo(token2)(dependency2), | ||
])(createContext()); | ||
|
||
// when reordered | ||
const context2 = registerAll([ | ||
bindTo(token2)(dependency2), | ||
bindTo(token1)(dependency1), | ||
])(createContext()); | ||
|
||
// then | ||
expect(lookup(context1)(token2)).toEqual(some('test_1_2')); | ||
expect(lookup(context2)(token2)).toEqual(some('test_1_2')); | ||
}); | ||
|
||
test('asks context for a eager reader dependency', () => { | ||
// given | ||
const token1 = createContextToken<string>(); | ||
const token2 = createContextToken<string>(); | ||
const token3 = createContextToken<string>(); | ||
const dependency1 = reader.map(() => 'test'); | ||
const dependency2 = reader.map(ask => ask(token1).map(v => v + '_1').getOrElse('')); | ||
const dependency3 = reader.map(ask => ask(token2).map(v => v + '_2').getOrElse('')); | ||
|
||
const context = registerAll([ | ||
bindTo(token1)(dependency1), | ||
bindTo(token2)(ctx => dependency2.run(ctx)), | ||
bindTo(token3)(dependency3), | ||
])(createContext()); | ||
|
||
// then | ||
expect(lookup(context)(token1)).toEqual(some('test')); | ||
expect(lookup(context)(token2)).toEqual(some('test_1')); | ||
expect(lookup(context)(token3)).toEqual(some('test_1_2')); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,12 @@ | ||
import { EffectMetadata } from './effects.interface'; | ||
import { AsyncScheduler } from 'rxjs/internal/scheduler/AsyncScheduler'; | ||
import { SchedulerLike } from 'rxjs'; | ||
import { InjectorGetter } from '../server/server.injector'; | ||
import { ContextProvider } from '../context/context.factory'; | ||
|
||
export const createEffectMetadata = <T extends SchedulerLike, U extends Error>( | ||
metadata: { inject: InjectorGetter, scheduler?: T, error?: U; } | ||
metadata: { ask: ContextProvider, scheduler?: T, error?: U; } | ||
): EffectMetadata<U> => ({ | ||
inject: metadata.inject, | ||
ask: metadata.ask, | ||
scheduler: metadata.scheduler || AsyncScheduler as any, | ||
error: metadata.error, | ||
}); |
Oops, something went wrong.