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
}
type Contextual<T> = (context: IContext) => Promise<T>
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>
find(userId: number): Contextual<ITeam[]>
find(): Promise<ITeam[]>
findUsers(teamId: number): Contextual<IUserInTeam[]>
findUsers(teamId: number): Promise<IUserInTeam[]>
// 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>) =>
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> = {
[K in keyof T]: K extends string
? T[K] extends (...args: any[]) => any

View File

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

View File

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

View File

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