Add Contextual<Service, Context> type

As seen in TeamService2.ts, the Contextual type will add Context as the
last argument to any method of the interface, as long as the method has
0-4 arguments.

Interfaces with more than 4 arguments cannot use this type, but they
could be converted to interfaces which use 1 argument
(object/dictionary) only.

Some long-term thinking: Maybe only methods with a single argument
should be supported, similar to the way gRPC does it.
This commit is contained in:
Jerko Steiner 2019-08-07 22:37:44 +07:00
parent cc2f5f58e2
commit 19565563cc
5 changed files with 48 additions and 23 deletions

View File

@ -24,24 +24,24 @@ export interface IContext {
userId: number userId: number
} }
type Contextual<T> = (context: IContext) => Promise<T>
export interface ITeamService { export interface ITeamService {
create(params: ITeamCreateParams): Contextual<ITeam> jerko(params: string): number
remove(params: ITeamRemoveParams): Contextual<{id: number}> create(params: ITeamCreateParams): Promise<ITeam>
update(params: ITeamUpdateParams): Contextual<ITeam> remove(params: ITeamRemoveParams): Promise<{id: number}>
addUser(params: ITeamAddUserParams): Contextual<IUserInTeam> update(params: ITeamUpdateParams): Promise<ITeam>
removeUser(params: ITeamAddUserParams): Contextual<ITeamAddUserParams> addUser(params: ITeamAddUserParams): Promise<IUserInTeam>
removeUser(params: ITeamAddUserParams): Promise<ITeamAddUserParams>
findOne(id: number): Promise<ITeam | undefined> findOne(id: number): Promise<ITeam | undefined>
find(userId: number): Contextual<ITeam[]> find(): Promise<ITeam[]>
findUsers(teamId: number): Contextual<IUserInTeam[]> findUsers(teamId: number): Promise<IUserInTeam[]>
// TODO add other methods // TODO add other methods
} }

View File

@ -9,6 +9,25 @@ type RetProm<T> = T extends Promise<any> ? T : Promise<T>
type PromisifyReturnType<T> = (...a: ArgumentTypes<T>) => type PromisifyReturnType<T> = (...a: ArgumentTypes<T>) =>
RetProm<UnwrapHOC<RetType<T>>> RetProm<UnwrapHOC<RetType<T>>>
/**
* Helps implement a service from a service definiton that has a context as a
* last argument.
*/
export type Contextual<T, Cx> = {
[K in keyof T]:
T[K] extends () => infer R
? (cx: Cx) => R :
T[K] extends (a: infer A) => infer R
? (a: A, cx: Cx) => R :
T[K] extends (a: infer A, b: infer B) => infer R
? (a: A, b: B, cx: Cx) => R :
T[K] extends (a: infer A, b: infer B, c: infer C) => infer R
? (a: A, b: B, c: C, cx: Cx) => R :
T[K] extends (a: infer A, b: infer B, c: infer C, d: infer D) => infer R
? (a: A, b: B, c: C, d: D, cx: Cx) => R :
never
}
export type FunctionPropertyNames<T> = { export type FunctionPropertyNames<T> = {
[K in keyof T]: K extends string [K in keyof T]: K extends string
? T[K] extends (...args: any[]) => any ? T[K] extends (...args: any[]) => any

View File

@ -8,16 +8,21 @@ import {
team as t, team as t,
IUserInTeam, IUserInTeam,
} from '@rondo/common' } from '@rondo/common'
import {Contextual} from '@rondo/jsonrpc'
type IContext = t.IContext type IContext = t.IContext
export class TeamService2 implements t.ITeamService { export class TeamService2 implements Contextual<t.ITeamService, IContext> {
constructor( constructor(
protected readonly db: DB, protected readonly db: DB,
protected readonly permissions: IUserPermissions, protected readonly permissions: IUserPermissions,
) {} ) {}
create = (params: t.ITeamCreateParams) => async (context: IContext) => { jerko(params: string, context?: IContext): number {
return parseInt(params, 10)
}
async create(params: t.ITeamCreateParams, context: IContext) {
const {userId} = context const {userId} = context
const name = trim(params.name) const name = trim(params.name)
@ -36,12 +41,12 @@ export class TeamService2 implements t.ITeamService {
userId, userId,
// ADMIN role // ADMIN role
roleId: 1, roleId: 1,
})(context) }, context)
return team return team
} }
remove = ({id}: t.ITeamRemoveParams) => async (context: IContext) => { async remove({id}: t.ITeamRemoveParams, context: IContext) {
const {userId} = context const {userId} = context
await this.permissions.belongsToTeam({ await this.permissions.belongsToTeam({
@ -58,7 +63,7 @@ export class TeamService2 implements t.ITeamService {
return {id} return {id}
} }
update = ({id, name}: t.ITeamUpdateParams) => async (context: IContext) => { async update({id, name}: t.ITeamUpdateParams, context: IContext) {
await this.permissions.belongsToTeam({ await this.permissions.belongsToTeam({
teamId: id, teamId: id,
userId: context.userId, userId: context.userId,
@ -74,7 +79,7 @@ export class TeamService2 implements t.ITeamService {
return (await this.findOne(id))! return (await this.findOne(id))!
} }
addUser = (params: t.ITeamAddUserParams) => async (context: IContext) => { async addUser(params: t.ITeamAddUserParams, context: IContext) {
const {userId, teamId, roleId} = params const {userId, teamId, roleId} = params
await this.db.getRepository(UserTeam) await this.db.getRepository(UserTeam)
.save({userId, teamId, roleId}) .save({userId, teamId, roleId})
@ -90,7 +95,7 @@ export class TeamService2 implements t.ITeamService {
return this.mapUserInTeam(userTeam!) return this.mapUserInTeam(userTeam!)
} }
removeUser = (params: t.ITeamAddUserParams) => async (context: IContext) => { async removeUser(params: t.ITeamAddUserParams, context: IContext) {
const {teamId, userId, roleId} = params const {teamId, userId, roleId} = params
await this.permissions.belongsToTeam({ await this.permissions.belongsToTeam({
@ -109,7 +114,8 @@ export class TeamService2 implements t.ITeamService {
return this.db.getRepository(Team).findOne(id) return this.db.getRepository(Team).findOne(id)
} }
find = () => async ({userId}: IContext) => { async find(context: IContext) {
const {userId} = context
return this.db.getRepository(Team) return this.db.getRepository(Team)
.createQueryBuilder('team') .createQueryBuilder('team')
.select('team') .select('team')
@ -118,7 +124,7 @@ export class TeamService2 implements t.ITeamService {
.getMany() .getMany()
} }
findUsers = (teamId: number) => async (context: IContext) => { async findUsers(teamId: number, context: IContext) {
await this.permissions.belongsToTeam({ await this.permissions.belongsToTeam({
teamId, teamId,
userId: context.userId, userId: context.userId,

View File

@ -4,8 +4,7 @@
"outDir": "esm" "outDir": "esm"
}, },
"references": [ "references": [
{ {"path": "../common/tsconfig.esm.json"},
"path": "../common/tsconfig.esm.json" {"path": "../jsonrpc/tsconfig.esm.json"}
}
] ]
} }

View File

@ -5,6 +5,7 @@
"rootDir": "src" "rootDir": "src"
}, },
"references": [ "references": [
{"path": "../common"} {"path": "../common"},
{"path": "../jsonrpc"}
] ]
} }