Skip to content

Commit ec9c480

Browse files
committed
feat: support constructor @dsource() decorator.
1 parent ce03e43 commit ec9c480

33 files changed

Lines changed: 470 additions & 77 deletions

core/README.md

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ import { User } from '../entities/user.entity';
5555

5656
@Controller('/api/users')
5757
export class UserController {
58+
constructor(@DSource() private dataSource: DataSource) {}
5859
@Get() // => GET /api/users
59-
public async getAll(@DSource() dataSource: DataSource): Promise<User[]> {
60-
return dataSource.manager.find(User);
60+
public async getAll(): Promise<User[]> {
61+
return this.dataSource.manager.find(User);
6162
}
6263
@Get('/:id') // => GET /api/users/:id
63-
public async getById(@Param('id') id: string, @DSource() dataSource: DataSource): Promise<User> {
64-
return dataSource.manager.findOne(User, id);
64+
public async getById(@Param('id') id: string): Promise<User> {
65+
return this.dataSource.manager.findOne(User, id);
6566
}
6667
@Post('/:id') // => POST /api/users/:id
6768
public async modify(@Body() body: { name: string; }): Promise<{ name: string; }> {
@@ -420,6 +421,26 @@ export class UserController {
420421

421422
**`@DSource()`** decorator injects you a [DataSource](https://typeorm.io/data-source-api) object.
422423

424+
Support constructor **`@DSource()`** decorator
425+
426+
```typescript
427+
import { Controller, Get, DSource, DataSource } from 'typenexus';
428+
import { Response, Request }from 'express';
429+
import { User } from '../entity/User.js';
430+
431+
@Controller('/users')
432+
export class UserController {
433+
constructor(@DSource() private dataSource: DataSource) {}
434+
@ContentType('application/json')
435+
@Get() // => GET /users
436+
public async getUsers(): Promise<User[]> {
437+
return this.dataSource.manager.find(User);
438+
}
439+
}
440+
```
441+
442+
Support parameter **`@DSource()`** decorator
443+
423444
```typescript
424445
import { Controller, Get, DSource, DataSource } from 'typenexus';
425446
import { Response, Request }from 'express';
@@ -719,10 +740,11 @@ import { User } from '../entity/User.js';
719740

720741
@Controller()
721742
export class UserController {
743+
constructor(@DSource() private dataSource: DataSource) {}
722744
@Delete("/users/:id")
723745
@OnUndefined(204)
724-
async remove(@Param("id") id: string, @DSource() dataSource: DataSource): Promise<void> {
725-
return dataSource.manager.findOneBy(User, { id });
746+
async remove(@Param("id") id: string): Promise<void> {
747+
return this.dataSource.manager.findOneBy(User, { id });
726748
}
727749
}
728750
```
@@ -735,10 +757,11 @@ import { User } from '../entity/User.js';
735757

736758
@Controller()
737759
export class UserController {
760+
constructor(@DSource() private dataSource: DataSource) {}
738761
@Delete("/users/:id")
739762
@OnUndefined(404)
740-
async remove(@Param("id") id: string, @DSource() dataSource: DataSource): Promise<void> {
741-
return dataSource.manager.findOneBy(User, { id });
763+
async remove(@Param("id") id: string): Promise<void> {
764+
return this.dataSource.manager.findOneBy(User, { id });
742765
}
743766
}
744767
```
@@ -761,10 +784,11 @@ import { User } from '../entity/User.js';
761784

762785
@Controller()
763786
export class UserController {
787+
constructor(@DSource() private dataSource: DataSource) {}
764788
@Get("/users/:id")
765789
@OnUndefined(UserNotFoundError)
766-
async remove(@Param("id") id: string, @DSource() dataSource: DataSource): Promise<void> {
767-
return dataSource.manager.findOneBy(User, { id });
790+
async remove(@Param("id") id: string): Promise<void> {
791+
return this.dataSource.manager.findOneBy(User, { id });
768792
}
769793
}
770794
```

core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"devDependencies": {
4747
"nodemon": "^2.0.20",
4848
"ts-node": "^10.9.1",
49-
"tsbb": "^4.1.2"
49+
"tsbb": "^4.1.5"
5050
},
5151
"dependencies": {
5252
"@babel/runtime": "^7.21.0",

core/src/ActionParameterHandler.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { DataSource } from 'typeorm';
12
import { Driver } from './Driver.js'
23
import { ParamMetadata } from './metadata/ParamMetadata.js';
4+
import { ParamConstructorMetadata } from './metadata/ParamConstructorMetadata.js';
35
import { Action } from './Action.js';
46
import { isPromiseLike } from './utils/isPromiseLike.js';
57
import { CurrentUserCheckerNotDefinedError } from './http-error/CurrentUserCheckerNotDefinedError.js';
@@ -10,6 +12,12 @@ import { AuthorizationRequiredError } from './http-error/AuthorizationRequiredEr
1012
*/
1113
export class ActionParameterHandler<T extends Driver> {
1214
constructor(private driver: T) {}
15+
/**
16+
* Handles action constructor parameter.
17+
*/
18+
handleConstructor(action: Action, param: ParamConstructorMetadata): DataSource {
19+
if (param.type === 'data-source') return action.dataSource;
20+
}
1321
/**
1422
* Handles action parameter.
1523
*/

core/src/Controllers.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,14 @@ export class Controllers<T extends Driver> {
5050
.map(param => {
5151
return this.parameterHandler.handle(action, param)
5252
});
53+
// compute all constructor parameters
54+
const paramsConstructor = actionMetadata.paramsConstructor
55+
.sort((param1, param2) => param1.index - param2.index)
56+
.map(param => this.parameterHandler.handleConstructor(action, param))
57+
.filter(Boolean);
5358

5459
return Promise.all(paramsPromises).then((params) => {
55-
const result = actionMetadata.callMethod(params, action);
60+
const result = actionMetadata.callMethod(params, paramsConstructor, action);
5661
return this.handleCallMethodResult(result, actionMetadata, action);
5762
})
5863
.catch(error => {

core/src/builder/MetadataBuilder.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { ControllerMetadata } from '../metadata/ControllerMetadata.js';
22
import { MiddlewareMetadata } from '../metadata/MiddlewareMetadata.js';
33
import { getMetadataArgsStorage } from '../metadata/MetadataArgsStorage.js';
44
import { ActionMetadata } from '../metadata/ActionMetadata.js';
5-
import { ActionMetadataArgs } from '../metadata/args/ActionMetadataArgs.js';
65
import { ResponseHandlerMetadata } from '../metadata/ResponseHandleMetadata.js';
76
import { ParamMetadata } from '../metadata/ParamMetadata.js';
7+
import { ParamConstructorMetadata } from '../metadata/ParamConstructorMetadata.js';
88
import { UseMetadata } from '../metadata/UseMetadata.js';
99
import { TypeNexusOptions } from '../DriverOptions.js';
1010

@@ -55,30 +55,31 @@ export class MetadataBuilder {
5555
* Creates action metadata.
5656
*/
5757
protected createActions(controller: ControllerMetadata): ActionMetadata[] {
58-
let target = controller.target;
59-
const actionsWithTarget: ActionMetadataArgs[] = [];
60-
while (target) {
61-
const actions = getMetadataArgsStorage()
62-
.filterActionsWithTarget(target)
63-
.filter(action => {
64-
return actionsWithTarget.map(a => a.method).indexOf(action.method) === -1;
58+
const actionsWithTarget: ActionMetadata[] = [];
59+
for (let target = controller.target; target; target = Object.getPrototypeOf(target)) {
60+
const actions = getMetadataArgsStorage().filterActionsWithTarget(target);
61+
const methods = actionsWithTarget.map(a => a.method);
62+
actions
63+
.filter(({ method }) => !methods.includes(method))
64+
.forEach(actionArgs => {
65+
const action = new ActionMetadata(controller, { ...actionArgs, target: controller.target }, this.options);
66+
action.params = this.createParams(action);
67+
action.paramsConstructor = this.createParamsConstructor(action);
68+
action.uses = this.createActionUses(action);
69+
action.build(this.createActionResponseHandlers(action));
70+
actionsWithTarget.push(action);
6571
});
66-
67-
actions.forEach(a => {
68-
a.target = controller.target;
69-
actionsWithTarget.push(a);
70-
});
71-
72-
target = Object.getPrototypeOf(target);
7372
}
73+
return actionsWithTarget;
74+
}
7475

75-
return actionsWithTarget.map(actionArgs => {
76-
const action = new ActionMetadata(controller, actionArgs, this.options);
77-
action.params = this.createParams(action);
78-
action.uses = this.createActionUses(action);
79-
action.build(this.createActionResponseHandlers(action));
80-
return action;
81-
});
76+
/**
77+
* Creates constructor param metadatas.
78+
*/
79+
protected createParamsConstructor(action: ActionMetadata): ParamConstructorMetadata[] {
80+
return getMetadataArgsStorage()
81+
.filterParamsConstructorWithTargetAndMethod('constructor')
82+
.map(paramArgs => new ParamConstructorMetadata(action, paramArgs));
8283
}
8384

8485
/**

core/src/decorator/DSource.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,22 @@ import { getMetadataArgsStorage } from '../metadata/MetadataArgsStorage.js';
22

33
export function DSource(): ParameterDecorator {
44
return function (object: Object, methodName: string, index: number) {
5-
getMetadataArgsStorage().params.push({
6-
type: 'data-source',
7-
object: object,
8-
method: methodName,
9-
index: index,
10-
});
5+
const storage = getMetadataArgsStorage();
6+
// Support `constructor` decorator definition
7+
if (!methodName && typeof index === 'number') {
8+
storage.paramsConstructor.push({
9+
type: 'data-source',
10+
object: object,
11+
method: 'constructor',
12+
index: index,
13+
});
14+
} else {
15+
storage.params.push({
16+
type: 'data-source',
17+
object: object,
18+
method: methodName,
19+
index: index,
20+
});
21+
}
1122
};
1223
}

core/src/metadata/ActionMetadata.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ActionMetadataArgs } from './args/ActionMetadataArgs.js';
44
import { ResponseHandlerMetadata } from './ResponseHandleMetadata.js';
55
import { Action } from '../Action.js';
66
import { ParamMetadata } from './ParamMetadata.js';
7+
import { ParamConstructorMetadata } from './ParamConstructorMetadata.js';
78
import { UseMetadata } from './UseMetadata.js';
89
import { TypeNexusOptions } from '../DriverOptions.js';
910

@@ -41,6 +42,11 @@ export class ActionMetadata {
4142
*/
4243
params: ParamMetadata[];
4344

45+
/**
46+
* Action's constructor parameters.
47+
*/
48+
paramsConstructor: ParamConstructorMetadata[] = [];
49+
4450
/**
4551
* Full route to this action (includes controller base route).
4652
*/
@@ -167,8 +173,8 @@ export class ActionMetadata {
167173
* Calls action method.
168174
* Action method is an action defined in a user controller.
169175
*/
170-
callMethod(params: any[], action: Action) {
171-
const controllerInstance = this.controllerMetadata.getInstance(action);
176+
callMethod(params: any[], paramsConstructor: any[], action: Action) {
177+
const controllerInstance = this.controllerMetadata.getInstance(action, paramsConstructor);
172178
return controllerInstance[this.method].apply(controllerInstance, params);
173179
}
174180

core/src/metadata/ControllerMetadata.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,25 @@ export interface UseContainerOptions {
1919
fallbackOnErrors?: boolean;
2020
}
2121

22-
23-
let userContainer: { get<T>(someClass: ClassConstructor<T> | Function, action?: Action): T };
22+
let userContainer: UserContainer;
2423
let userContainerOptions: UseContainerOptions;
25-
export type ClassConstructor<T> = { new (...args: any[]): T };
24+
export type ClassConstructor<T, K = unknown> = { new (...args: K[]): T };
2625

26+
interface UserContainer {
27+
get<T, K = unknown>(someClass: ClassConstructor<T, K> | Function, params?: K[], action?: Action): T
28+
}
2729
/**
2830
* Container to be used by this library for inversion control. If container was not implicitly set then by default
2931
* container simply creates a new instance of the given class.
3032
*/
31-
const defaultContainer: { get<T>(someClass: ClassConstructor<T> | Function): T } = new (class {
33+
const defaultContainer: UserContainer = new (class {
3234
private instances: { type: Function; object: any }[] = [];
33-
get<T>(someClass: ClassConstructor<T>): T {
35+
get<T, K = unknown>(someClass: ClassConstructor<T, K>, paramsConstructor: K[] = []): T {
3436
let instance = this.instances.find(instance => instance.type === someClass);
3537
if (!instance) {
36-
instance = { type: someClass, object: new someClass() };
38+
instance = { type: someClass, object: new someClass(...paramsConstructor) };
3739
this.instances.push(instance);
3840
}
39-
4041
return instance.object;
4142
}
4243
})();
@@ -46,18 +47,18 @@ const defaultContainer: { get<T>(someClass: ClassConstructor<T> | Function): T }
4647
* @param someClass A class constructor to resolve
4748
* @param action The request/response context that `someClass` is being resolved for
4849
*/
49-
export function getFromContainer<T>(someClass: ClassConstructor<T> | Function, action?: Action): T {
50+
export function getFromContainer<T, K = unknown>(someClass: ClassConstructor<T, K> | Function, action?: Action, paramsConstructor?: any[]): T {
5051
if (userContainer) {
5152
try {
52-
const instance = userContainer.get(someClass, action);
53+
const instance = userContainer.get(someClass, paramsConstructor || [], action);
5354
if (instance) return instance;
5455

5556
if (!userContainerOptions || !userContainerOptions.fallback) return instance;
5657
} catch (error) {
5758
if (!userContainerOptions || !userContainerOptions.fallbackOnErrors) throw error;
5859
}
5960
}
60-
return defaultContainer.get<T>(someClass);
61+
return defaultContainer.get<T, K>(someClass, paramsConstructor || []);
6162
}
6263

6364

@@ -104,8 +105,8 @@ export class ControllerMetadata {
104105
* Gets instance of the controller.
105106
* @param action Details around the request session
106107
*/
107-
getInstance(action: Action): any {
108-
return getFromContainer(this.target, action);
108+
getInstance(action: Action, paramsConstructor?: any[]): any {
109+
return getFromContainer(this.target, action, paramsConstructor);
109110
}
110111
/**
111112
* Builds everything controller metadata needs.

core/src/metadata/MetadataArgsStorage.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ActionMetadataArgs } from './args/ActionMetadataArgs.js';
33
import { MiddlewareMetadataArgs } from './args/MiddlewareMetadataArgs.js';
44
import { ResponseHandlerMetadataArgs } from './args/ResponseHandleMetadataArgs.js';
55
import { ParamMetadataArgs } from './args/ParamMetadataArgs.js';
6+
import { ParamConstructorMetadataArgs } from './args/ParamConstructorMetadataArgs.js';
67
import { UseMetadataArgs } from './args/UseMetadataArgs.js';
78

89
/**
@@ -33,6 +34,10 @@ export class MetadataArgsStorage {
3334
* Registered param metadata args.
3435
*/
3536
params: ParamMetadataArgs[] = [];
37+
/**
38+
* Registered constructor param metadata args.
39+
*/
40+
paramsConstructor: ParamConstructorMetadataArgs[] = [];
3641
/**
3742
* Registered response handler metadata args.
3843
*/
@@ -67,6 +72,14 @@ export class MetadataArgsStorage {
6772
});
6873
}
6974

75+
/**
76+
* Filters constructor parameters by a given classes.
77+
*/
78+
filterParamsConstructorWithTargetAndMethod(methodName: string): ParamConstructorMetadataArgs[] {
79+
return this.paramsConstructor.filter(param => {
80+
return param.method === methodName;
81+
});
82+
}
7083
/**
7184
* Filters parameters by a given classes.
7285
*/

0 commit comments

Comments
 (0)