Gathering detailed insights and metrics for @gapi/core
Gathering detailed insights and metrics for @gapi/core
Gathering detailed insights and metrics for @gapi/core
Gathering detailed insights and metrics for @gapi/core
@gapi/playground
##### For questions/issues you can write ticket [here](http://gitlab.youvolio.com/Stradivario/gapi-auth/issues) ##### This module is intended to be used with [rxdi](https://github.com/rxdi/core) or [gapi](https://github.com/Stradivario/gapi)
@gapi/auth
##### For questions/issues you can write ticket [here](http://gitlab.youvolio.com/Stradivario/gapi-auth/issues) ##### This module is intended to be used with [rxdi](https://github.com/rxdi/core) or [gapi](https://github.com/Stradivario/gapi)
@gapi/sendgrid
```ts import { SendgridModule } from '@gapi/sendgrid'; import { Module } from '@rxdi/core';
@gapi/main
[](https://travis-ci.org/Stradivario/gapi-core)
Inspired by Angular intended to provide complex Nodejs Graphql Backend applications with minimum effort.
npm install @gapi/core
Typescript
Module System
Node Version
NPM Version
JavaScript (95.09%)
TypeScript (4.89%)
HTML (0.01%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
27 Stars
1,656 Commits
2 Forks
2 Watchers
38 Branches
1 Contributors
Updated on Feb 11, 2025
Latest Version
1.8.151
Package Id
@gapi/core@1.8.151
Unpacked Size
77.86 kB
Size
19.98 kB
File Count
18
NPM Version
6.14.18
Node Version
14.21.3
Published on
Jun 18, 2024
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
Really easy GraphQL API framework build on top of NodeJS inspired by @Angular
Build with @rxdi - reactive Extension Dependency Injection Container working inside Browser and Node
Created to provide complex backend scalable applications with minimum effort.
For questions/issues you can write ticket here
Starting project for less than 1 minute via Gapi-CLI
Heroku Ready!
Basic starter project has Heroku Easy Deploy Button!
Amazon ServerLess Ready!
Part of the frameworks and techniques used without which Gapi would not exist :love_letter:
IoC Containers are DI frameworks that can work outside of the programming language. In some you can configure which implementations to use in metadata files (e.g. XML) which are less invasive. With some you can do IoC that would normally be impossible like inject an implementation at pointcuts.
Pointcuts - aspect-oriented computer programming
Advanced Starter Example and production build
Video Tutorials
1npm install @gapi/core
1import { CoreModule } from '@gapi/core'; 2import { Controller, Module, Bootstrap } from '@rxdi/core'; 3import { Query, Type } from '@rxdi/graphql'; 4import { GraphQLObjectType, GraphQLInt, GraphQLNonNull } from 'graphql'; 5 6export const UserType = new GraphQLObjectType({ 7 name: 'UserType', 8 fields: () => ({ 9 id: { 10 type: GraphQLInt, 11 }, 12 }), 13}); 14 15@Controller() 16export class UserQueriesController { 17 @Type(UserType) 18 @Query({ 19 id: { 20 type: new GraphQLNonNull(GraphQLInt), 21 }, 22 }) 23 findUser(root, { id }, context): UserType { 24 return { id: id }; 25 } 26} 27 28@Module({ 29 imports: [CoreModule.forRoot()], 30 controllers: [UserQueriesController], 31}) 32export class AppModule {} 33 34Bootstrap(AppModule).subscribe();
You need to create tsconfig.json
with following content
1{ 2 "compilerOptions": { 3 "emitDecoratorMetadata": true, 4 "experimentalDecorators": true 5 } 6}
Execute
1ts-node index.ts
Or execute the following command to set these options on runtime:
1export TS_NODE_COMPILER_OPTIONS='{"experimentalDecorators": true, "emitDecoratorMetadata": true}' && 2ts-node index.ts --compilerOptions $TS_NODE_COMPILER_OPTIONS
To add configuration click here
https://github.com/Stradivario/gapi/tree/master/packages/federation
1import { FederationModule } from '@gapi/federation'; 2import { Bootstrap } from '@rxdi/core'; 3 4Bootstrap( 5 FederationModule.forRoot({ 6 port: 4000, 7 willSendRequest({ request, context }) { 8 request.http.headers.set('authorization', context.headers.authorization); 9 }, 10 serviceList: [ 11 { name: 'accounts', url: 'http://localhost:9000/graphql' }, 12 { name: 'products', url: 'http://localhost:9001/graphql' }, 13 ], 14 }), 15).subscribe(() => console.log('started'));
Or
1import { ApolloServer } from 'apollo-server'; 2import { ApolloGateway, RemoteGraphQLDataSource } from '@apollo/gateway'; 3 4const serviceList = [ 5 { 6 name: 'accounts', 7 url: 'http://localhost:9000/graphql', 8 }, 9]; 10 11class AuthenticatedDataSource extends RemoteGraphQLDataSource { 12 willSendRequest({ request, context }) { 13 request.http.headers.set('authorization', context.authorization); 14 } 15} 16const gateway = new ApolloGateway({ 17 serviceList, 18 buildService: ({ url }) => new AuthenticatedDataSource({ url }), 19 __exposeQueryPlanExperimental: true, 20}); 21 22(async () => { 23 const server = new ApolloServer({ 24 gateway, 25 engine: false, 26 context: ({ 27 req: { 28 headers: { authorization }, 29 }, 30 }) => ({ authorization }), 31 subscriptions: false, 32 }); 33 34 const { url } = await server.listen({ port: 4000 }); 35 console.log(`🚀 Apollo Gateway ready at ${url}`); 36})();
1npm install -g nodemon ts-node
1npm install @gapi/cli -g
It may take 20 seconds because it will install project dependencies.
1gapi new my-project
1gapi new my-project --advanced
1gapi new my-project --microservices
1gapi new my-project --serverless
1gapi new my-project --serverless-sequelize
Enter inside my-project and type:
1npm start
1http://localhost:9000/graphiql
1gapi test
1gapi start
1gapi test --watch
1if (process.env.BEFORE_HOOK) { 2 // do something here 3}
1gapi test --before
1import { Container } from '@rxdi/core'; 2import { AuthPrivateService } from './auth.service'; 3 4const authService: AuthPrivateService = Container.get(AuthPrivateService); 5 6describe('Auth Service', () => { 7 it('unit: signJWTtoken => token : Should sucessfully sign jwt', async (done) => { 8 const token = authService.signJWTtoken({ 9 email: 'dada@abv.bg', 10 id: 1, 11 scope: ['ADMIN'], 12 }); 13 expect(token).toBeTruthy(); 14 const verifyedToken = authService.verifyToken(token); 15 expect(verifyedToken.email).toBe('dada@abv.bg'); 16 expect(verifyedToken.id).toBe(1); 17 expect(verifyedToken.scope[0]).toBe('ADMIN'); 18 done(); 19 }); 20});
Filepath: root/src/app/user/user-queries.controller.e2e.spec.ts
1import { Container } from '@rxdi/core'; 2import { IQuery } from '../core/api-introspection/index'; 3import { TestUtil } from '../core/test-util/testing.service'; 4import { map } from 'rxjs/operators'; 5 6const testUtil: TestUtil = Container.get(TestUtil); 7 8describe('User Queries Controller', () => { 9 it('e2e: queries => (findUser) : Should sucessfully find user', async (done) => { 10 testUtil 11 .sendRequest<IQuery>({ 12 query: ` 13 query findUser($id: Int!) { 14 findUser(id: $id) { 15 id 16 settings { 17 username 18 firstname 19 } 20 } 21 } 22 `, 23 variables: { 24 id: 1, 25 }, 26 }) 27 .pipe( 28 map((res) => { 29 expect(res.success).toBeTruthy(); 30 return res.data.findUser; 31 }) 32 ) 33 .subscribe( 34 async (res) => { 35 expect(res.id).toBe(1); 36 expect(res.settings.username).toBe('o'); 37 expect(res.settings.firstname).toBe('pesho'); 38 done(); 39 }, 40 (err) => { 41 expect(err).toBe(null); 42 done(); 43 } 44 ); 45 }); 46});
Filepath: root/src/app/core/test-util/testing.service.ts
1 disableAuthorization() { 2 this.tester = tester({ url: process.env.ENDPOINT_TESTING, contentType: 'application/json' }); 3 } 4 5 enableAuthorization() { 6 this.tester = tester({ url: process.env.ENDPOINT_TESTING, contentType: 'application/json', authorization: process.env.TOKEN_TESTING}); 7 } 8 9 sendRequest<T>(query: SendRequestQueryType): Observable<Response<T>> { 10 if (query.signiture) { 11 this.tester = tester({ 12 url: process.env.ENDPOINT_TESTING, 13 contentType: 'application/json', 14 authorization: query.signiture.token 15 }); 16 } 17 return from(this.tester(JSON.stringify(query))); 18 }
1import { Effect } from '@rxdi/core'; 2import { OfType } from '@rxdi/graphql'; 3import { EffectTypes } from '../core/api-introspection/EffectTypes'; 4 5@Effect() 6export class UserEffects { 7 @OfType<EffectTypes>(EffectTypes.login) 8 findUser(result, payload, context) { 9 console.log(result, payload, context); 10 } 11}
How it works ?
When the application starts the whole schema is collected via Decorators applied inside GapiControllers.Now when we have our schema collected and bootstraping is done next step is to attach all BehaviorSubject Observables to particular resolver and from that step we got Type based string literal enums a.k.a Gapi Effects.They look like that:
1function strEnum<T extends string>(o: Array<T>): { [K in T]: K } { 2 return o.reduce((res, key) => { 3 res[key] = key; 4 return res; 5 }, Object.create(null)); 6} 7export const EffectTypes = strEnum([ 8 'myevent', 9 'login', 10 'subscribeToUserMessagesBasic', 11 'subscribeToUserMessagesWithFilter', 12 'destroyUser', 13 'updateUser', 14 'addUser', 15 'publishSignal', 16]); 17export type EffectTypes = keyof typeof EffectTypes;
Import GapiEffect inside Module
1import { Module } from '@rxdi/core'; 2import { UserQueriesController } from './user-queries.controller'; 3import { UserSubscriptionsController } from './user-subscriptions.controller'; 4import { UserMutationsController } from './user-mutations.controller'; 5import { UserService } from './services/user.service'; 6import { AnotherService } from './services/another.service'; 7import { UserEffects } from './user.effects'; 8 9@Module({ 10 controllers: [ 11 UserQueriesController, 12 UserSubscriptionsController, 13 UserMutationsController, 14 ], 15 services: [UserService, AnotherService], 16 effects: [UserEffects], 17}) 18export class UserModule {}
If you want to create custom Effect for particular resolver you need to use @EffectName('myevent') Decorator takes String This decorator will override default event which is Name of the Method in this example will be findUser
1 @Type(UserType) 2 @EffectName('myevent') 3 @Query({ 4 id: { 5 type: new GraphQLNonNull(GraphQLInt) 6 } 7 }) 8 findUser(root, { id }, context) { 9 return { 10 id: 1 11 }; 12 }
If you click Save app will automatically reload and you will have such a typing inside autogenerated EffectTypes from api-introspection Then you can lock it with new Typo generated "myevent"
1 @Type(UserType) 2 @EffectName(EffectTypes.myevent) 3 @Query({ 4 id: { 5 type: new GraphQLNonNull(GraphQLInt) 6 } 7 }) 8 findUser(root, { id }, context) { 9 return { 10 id: 1 11 }; 12 }
Purpose of Effects is to create logic after particular resolver is triggered Successfully without thrown error for example:
How to register plugin to the system.
Difference between Plugin and Service is that system will trigger register method inside constructor if exist,
else it will resolve OnInit and constructor properties.
That way you can register your own plugins to the system after everything is bootstrapped.
If you decide for example to return Promise on register
and resolve it inside setTimeout(() =>resolve(data), 1000) the app will wait till the promise is resolved.
1import { Module, Plugin, Service, PluginInterface } from '@rxdi/core'; 2import { HAPI_SERVER } from '@rxdi/hapi'; 3import { Server } from '@hapi/hapi'; 4 5@Service() 6export class TestService { 7 testMethod() { 8 return 'Hello world'; 9 } 10} 11 12@Plugin() 13export class MyHapiPlugin implements PluginInterface { 14 name = 'MyPlugin'; 15 version = '1.0.0'; 16 17 constructor( 18 private testService: TestService, 19 @Inject(HAPI_SERVER) private server: Server 20 ) {} 21 22 async register() { 23 this.server.route({ 24 method: 'GET', 25 path: '/test', 26 handler: this.handler.bind(this), 27 }); 28 } 29 30 async handler(request, h) { 31 return this.testService.helloWorld(); 32 } 33} 34 35@Module({ 36 plugins: [MyHapiPlugin], 37 services: [TestService], 38}) 39export class AppModule {}
1import { Module, ModuleWithServices, InjectionToken } from '@rxdi/core'; 2import { of } from 'rxjs'; 3 4@Service() 5export class MODULE_DI_CONFIG { 6 text: string = 'Hello world'; 7} 8 9const MY_MODULE_CONFIG = new InjectionToken<MODULE_DI_CONFIG>( 10 'my-module-config' 11); 12 13@Module({ 14 imports: [], 15}) 16export class YourModule { 17 public static forRoot(): ModuleWithServices { 18 return { 19 module: YourModule, 20 services: [ 21 { provide: MY_MODULE_CONFIG, useValue: { text: 'Hello world' } }, 22 { provide: MY_MODULE_CONFIG, useClass: MODULE_DI_CONFIG }, 23 { 24 provide: MY_MODULE_CONFIG, 25 useFactory: () => ({ text: 'Hello world' }), 26 }, 27 { 28 provide: MY_MODULE_CONFIG, 29 lazy: true, // Will be evaluated and resolved if false will remain Promise 30 useFactory: async () => 31 await Promise.resolve({ text: 'Hello world' }), 32 }, 33 { 34 provide: MY_MODULE_CONFIG, 35 lazy: true, // Will be evaluated and resolved if false will remain Observable 36 useFactory: () => of({ text: 'Hello world' }), 37 }, 38 { 39 // this example will download external module from link and save it inside node modules 40 // then will load it inside createUniqueHash token or MY_MODULE_CONFIG. 41 provide: MY_MODULE_CONFIG, 42 useDynamic: { 43 fileName: 'createUniqueHash', 44 namespace: '@helpers', 45 extension: 'js', 46 typings: '', 47 outputFolder: '/node_modules/', 48 link: 49 'https://ipfs.infura.io/ipfs/QmdQtC3drfQ6M6GFpDdrhYRKoky8BycKzWbTkc4NEzGLug', 50 }, 51 }, 52 ], 53 }; 54 } 55}
(#amazon-serverless)
Inside main.ts
1import { AppModule } from './app/app.module'; 2import { BootstrapFramework, Container } from '@rxdi/core'; 3import { FrameworkImports } from './framework-imports'; 4import { format } from 'url'; 5import { HAPI_SERVER } from '@rxdi/hapi'; 6import { Server } from '@hapi/hapi'; 7 8const App = BootstrapFramework(AuthMicroserviceModule, [FrameworkImports], { 9 init: true, 10}).toPromise(); 11 12export const handler = async (event, context, callback) => { 13 const app = await App; 14 const url = format({ 15 pathname: event.path, 16 query: event.queryStringParameters, 17 }); 18 const options = { 19 method: event.httpMethod, 20 url, 21 payload: event.body, 22 headers: event.headers, 23 validate: false, 24 }; 25 let res = { 26 statusCode: 502, 27 result: null, 28 }; 29 try { 30 res = await Container.get<Server>(HAPI_SERVER).inject(options); 31 } catch (e) { 32 console.error(JSON.stringify(e)); 33 } 34 const headers = Object.assign({ 35 'Access-Control-Allow-Origin': '*', 36 'Access-Control-Allow-Methods': 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT', 37 }); 38 return { 39 statusCode: res.statusCode, 40 body: res.result, 41 headers, 42 }; 43};
If we want to use AWS Lambdas Offline we need to set RandomPort to true inside HapiConfig because HAPI will generate random PORT everytime and as far as i know lambas are independent server which will be started everytime when someone execute that particular function.So when running offline it will not say that port 9000 is opened with another server.
1import { AppModule } from './app/app.module'; 2import { BootstrapFramework } from '@rxdi/core'; 3import { CoreModule } from '@gapi/core'; 4 5const GapiCoreModule = CoreModule.forRoot({ 6 server: { 7 randomPort: true, 8 hapi: { 9 port: 9000, 10 }, 11 }, 12 graphql: { 13 path: '/graphql', 14 openBrowser: false, 15 writeEffects: false, 16 graphiQlPath: '/graphiql', 17 graphiqlOptions: { 18 endpointURL: '/graphql', 19 subscriptionsEndpoint: `ws://localhost:9000/subscriptions`, 20 websocketConnectionParams: { 21 token: process.env.GRAPHIQL_TOKEN, 22 }, 23 }, 24 graphqlOptions: { 25 schema: null, 26 }, 27 }, 28}); 29BootstrapFramework(AppModule, [GapiCoreModule], { 30 init: false, 31 initOptions: { 32 effects: true, 33 plugins: true, 34 services: true, 35 controllers: true, 36 }, 37 logger: { 38 logging: true, 39 date: true, 40 exitHandler: true, 41 fileService: true, 42 hashes: true, 43 }, 44}).subscribe( 45 () => console.log('Started!'), 46 (e) => console.error(e) 47);
Lambdas cannot use Typescript so we need to compile our application to es6 as commonjs module
1tsc
Create serverless.yml
1service: gapi-serverless 2provider: 3 name: aws 4 runtime: nodejs8.10 5 stage: development 6 profile: default 7 region: us-east-2 8 9functions: 10 root: 11 handler: src/main.handler 12 events: 13 - http: 14 path: '/{proxy+}' 15 method: any 16 cors: true 17 integration: lambda-proxy 18 19plugins: 20 - serverless-offline
Then we can run
1serverless deploy
Later you can create PROXY server and map all existing Lambdas as a single GRAPHQL Schema
1import { Module } from '@rxdi/core'; 2import { MicroserviceModule } from '@gapi/microservices'; 3import { CoreModule } from './core/core.module'; 4 5@Module({ 6 imports: [ 7 CoreModule, 8 MicroserviceModule.forRoot([ 9 { 10 name: 'microservice1', 11 link: 12 'https://hkzdqnc1i2.execute-api.us-east-2.amazonaws.com/development/graphql', 13 }, 14 ]), 15 ], 16}) 17export class AppModule {}
(#gapi-configuration)
Since 1.0.0 version @gapi micrated to @rxdi there is a little changes which are with Configuration @gapi now uses @rxdi/core infrastructure so it is a Framework created on top of it This @gapi/core module is wrapper for 3 modules from @rxdi infrastructure: @rxdi/graphql, @rxdi/graphql-pubsub, @rxdi/hapi When you install @gapi/core automatically will be installed these 3 modules and inside of @gapi/core module there is a Default configuration for CoreModule which this module exports { CoreModule } example:
1const DEFAULT_CONFIG = { 2 server: { 3 hapi: { 4 port: 9000 5 }, 6 }, 7 graphql: { 8 path: '/graphql', 9 openBrowser: true, 10 writeEffects: true, 11 graphiql: true, 12 graphiQlPlayground: false, 13 graphiQlPath: '/graphiql', 14 watcherPort: '', 15 graphiqlOptions: { 16 endpointURL: '/graphql', 17 subscriptionsEndpoint: `${ 18 process.env.GRAPHIQL_WS_SSH ? 'wss' : 'ws' 19 }://${process.env.GRAPHIQL_WS_PATH || 'localhost'}${ 20 process.env.DEPLOY_PLATFORM === 'heroku' 21 ? '' 22 : `:${process.env.API_PORT || 23 process.env.PORT}` 24 }/subscriptions`, 25 websocketConnectionParams: { 26 token: process.env.GRAPHIQL_TOKEN 27 } 28 }, 29 graphqlOptions: { 30 schema: null 31 } 32 }, 33}; 34...
Full example can be checked here
Based on that change if we want to not use default configuration instead of this
1BootstrapFramework(AppModule, [CoreModule]).subscribe();
We need to add more complex configuration and call it framework-imports where our configuration will live This module will be imported before AppModule will bootstrap.
1import { CoreModule, Module } from '@gapi/core'; 2import { AuthService } from './app/core/services/auth/auth.service'; 3import { AuthModule } from '@gapi/auth'; 4import { readFileSync } from 'fs'; 5 6@Module({ 7 imports: [ 8 AuthModule.forRoot({ 9 algorithm: 'HS256', 10 cert: readFileSync('./cert.key'), 11 cyper: { 12 algorithm: 'aes256', 13 iv: 'Jkyt1H3FA8JK9L3B', 14 privateKey: '8zTVzr3p53VC12jHV54rIYu2545x47lA', 15 }, 16 }), 17 CoreModule.forRoot({ 18 server: { 19 hapi: { 20 port: process.env.API_PORT || process.env.PORT || 9000, 21 }, 22 }, 23 pubsub: { 24 authentication: AuthService, 25 }, 26 graphql: { 27 path: process.env.GRAPHQL_PATH, 28 openBrowser: process.env.OPEN_BROWSER === 'true' ? true : false, 29 watcherPort: 8967, 30 writeEffects: process.env.WRITE_EFFECTS === 'true' ? true : false, 31 graphiql: process.env.GRAPHIQL === 'true' ? true : false, 32 graphiQlPlayground: 33 process.env.ENABLE_GRAPHIQL_PLAYGROUND === 'true' ? true : false, 34 graphiQlPath: process.env.GRAPHIQL_PATH, 35 authentication: AuthService, 36 graphiqlOptions: { 37 endpointURL: process.env.GRAPHQL_PATH, 38 passHeader: `'Authorization':'${process.env.GRAPHIQL_TOKEN}'`, 39 subscriptionsEndpoint: `${ 40 process.env.GRAPHIQL_WS_SSH ? 'wss' : 'ws' 41 }://${process.env.GRAPHIQL_WS_PATH || 'localhost'}${ 42 process.env.DEPLOY_PLATFORM === 'heroku' 43 ? '' 44 : `:${process.env.API_PORT || process.env.PORT}` 45 }/subscriptions`, 46 websocketConnectionParams: { 47 token: process.env.GRAPHIQL_TOKEN, 48 }, 49 }, 50 graphqlOptions: { 51 schema: null, 52 }, 53 }, 54 }), 55 ], 56}) 57export class FrameworkImports {}
Now you can import FrameworkImports
1import { BootstrapFramework } from '@rxdi/core'; 2import { FrameworkImports } from './framework-imports'; 3import { AppModule } from './app/app.module'; 4 5BootstrapFramework(AppModule, [FrameworkImports]).subscribe();
1gapi app build
1gapi app start
1gapi app stop
1gapi workers start
1gapi workers stop
1import { Module } from '@rxdi/core'; 2import { UserModule } from './user/user.module'; 3import { CoreModule } from './core/core.module'; 4 5@Module({ 6 imports: [UserModule, CoreModule], 7}) 8export class AppModule {}
1import { Module } from '@rxdi/core'; 2import { UserQueriesController } from './user-queries.controller'; 3import { UserSubscriptionsController } from './user-subscriptions.controller'; 4import { UserMutationsController } from './user-mutations.controller'; 5import { UserService } from './services/user.service'; 6import { AnotherService } from './services/another.service'; 7import { UserEffect } from './user.effect'; 8 9@Module({ 10 controllers: [ 11 UserQueriesController, 12 UserSubscriptionsController, 13 UserMutationsController, 14 ], 15 services: [UserService, AnotherService], 16 effects: [UserEffect], 17}) 18export class UserModule {}
1import { GapiObjectType, Resolve, InjectType } from '@rxdi/graphql'; 2import { GraphQLScalarType, GraphQLInt, GraphQLString } from 'graphql'; 3import { UserSettings } from './user.settings'; 4 5@GapiObjectType() 6export class UserType { 7 readonly id: number | GraphQLScalarType = GraphQLInt; 8 readonly email: string | GraphQLScalarType = GraphQLString; 9 readonly type: string | GraphQLScalarType = GraphQLString; 10 readonly password: string | GraphQLScalarType = GraphQLString; 11 readonly name: string | GraphQLScalarType = GraphQLString; 12 readonly settings: UserSettings = InjectType(UserSettings); 13 14 @Resolve('id') 15 getId?(root, payload, context) { 16 return 1; 17 } 18}
1import { Injector } from '@rxdi/core'; 2import { GapiObjectType, Resolve } from '@rxdi/graphql'; 3import { AnotherService } from '../services/another.service'; 4import { GraphQLScalarType, GraphQLBoolean } from 'graphql'; 5 6@GapiObjectType() 7export class UserSettings { 8 @Injector(AnotherService) private anotherService?: AnotherService; 9 10 readonly sidebar: boolean | GraphQLScalarType = GraphQLBoolean; 11 12 @Resolve('sidebar') 13 async getSidebar?(root, payload, context) { 14 return await this.anotherService.returnTrueAsync(); 15 } 16}
1import { GapiObjectType } from '@rxdi/graphql'; 2import { GraphQLScalarType, GraphQLString } from 'graphql'; 3 4@GapiObjectType() 5export class UserMessage { 6 readonly message: string | GraphQLScalarType = GraphQLString; 7}
1import { GapiObjectType, InjectType } from '@rxdi/graphql'; 2import { UserType } from './user.type'; 3import { GraphQLScalarType, GraphQLString } from 'graphql'; 4 5@GapiObjectType() 6export class UserTokenType { 7 readonly token: string | GraphQLScalarType = GraphQLString; 8 readonly user: UserType = InjectType(UserType); 9}
1import { Controller } from '@rxdi/core'; 2import { Type, Query, Public } from '@rxdi/graphql'; 3import { GraphQLNonNull, GraphQLInt, GraphQLString } from 'graphql'; 4import { UserService } from './services/user.service'; 5import { UserType } from './types/user.type'; 6import { UserTokenType } from './types/user-login.type'; 7import { AuthPrivateService } from '../core/services/auth/auth.service'; 8import { IUserType, IUserTokenType } from '../core/api-introspection/index'; 9 10@Controller() 11export class UserQueriesController { 12 constructor( 13 private userService: UserService, 14 private authService: AuthPrivateService 15 ) {} 16 17 @Type(UserType) 18 @Public() 19 @Query({ 20 id: { 21 type: new GraphQLNonNull(GraphQLInt), 22 }, 23 }) 24 findUser(root, { id }, context): IUserType { 25 return this.userService.findUser(id); 26 } 27 28 @Type(UserTokenType) 29 @Public() 30 @Query({ 31 email: { 32 type: new GraphQLNonNull(GraphQLString), 33 }, 34 password: { 35 type: new GraphQLNonNull(GraphQLString), 36 }, 37 }) 38 login(root, { email, password }, context) { 39 let credential: IUserTokenType; 40 41 // Find user from database 42 const user = <IUserType>{ 43 id: 1, 44 email: email, 45 type: 'ADMIN', 46 settings: { 47 sidebar: true, 48 }, 49 password: this.authService.encryptPassword(password), 50 name: 'Test Testov', 51 }; 52 53 if (this.authService.decryptPassword(user.password) === password) { 54 credential = { 55 user: user, 56 token: this.authService.signJWTtoken({ 57 email: user.email, 58 id: user.id, 59 scope: [user.type], 60 }), 61 }; 62 } else { 63 throw new Error('missing-username-or-password'); 64 } 65 return credential; 66 } 67}
1import { Controller } from '@rxdi/core'; 2import { UserService } from './services/user.service'; 3import { UserType } from './types/user.type'; 4import { UserMessage } from './types/user-message.type'; 5import { IUserType } from '../core/api-introspection'; 6import { PubSubService } from '@rxdi/graphql-pubsub'; 7import { Scope, Type, Mutation, Public } from '@rxdi/graphql'; 8import { GraphQLNonNull, GraphQLString, GraphQLInt } from 'graphql'; 9 10@Controller() 11export class UserMutationsController { 12 constructor( 13 private userService: UserService, 14 private pubsub: PubSubService 15 ) {} 16 17 @Scope('ADMIN') 18 @Type(UserMessage) 19 @Public() 20 @Mutation({ 21 message: { 22 type: new GraphQLNonNull(GraphQLString), 23 }, 24 signal: { 25 type: new GraphQLNonNull(GraphQLString), 26 }, 27 }) 28 publishSignal(root, { message, signal }, context): UserMessage { 29 console.log( 30 `${signal} Signal Published message: ${message} by ${context.email}` 31 ); 32 this.pubsub.publish( 33 signal, 34 `${signal} Signal Published message: ${message} by ${context.email}` 35 ); 36 return { message }; 37 } 38 39 @Scope('ADMIN') 40 @Type(UserType) 41 @Mutation({ 42 id: { 43 type: new GraphQLNonNull(GraphQLInt), 44 }, 45 }) 46 deleteUser(root, { id }, context): IUserType { 47 return this.userService.deleteUser(id); 48 } 49 50 @Scope('ADMIN') 51 @Type(UserType) 52 @Mutation({ 53 id: { 54 type: new GraphQLNonNull(GraphQLInt), 55 }, 56 }) 57 updateUser(root, { id }, context): IUserType { 58 return this.userService.updateUser(id); 59 } 60 61 @Scope('ADMIN') 62 @Type(UserType) 63 @Mutation({ 64 id: { 65 type: new GraphQLNonNull(GraphQLInt), 66 }, 67 }) 68 addUser(root, { id }, context): IUserType { 69 return this.userService.addUser(id); 70 } 71}
1import { Controller } from '@rxdi/core'; 2import { Type, Scope, Public } from '@rxdi/graphql'; 3import { withFilter } from 'graphql-subscriptions'; 4import { GraphQLNonNull, GraphQLInt } from 'graphql'; 5import { PubSubService, Subscribe, Subscription } from '@rxdi/graphql-pubsub'; 6import { UserMessage } from './types/user-message.type'; 7 8@Controller() 9export class UserSubscriptionsController { 10 constructor(private pubsub: PubSubService) {} 11 12 @Scope('ADMIN') 13 @Type(UserMessage) 14 @Public() 15 @Subscribe((self: UserSubscriptionsController) => 16 self.pubsub.asyncIterator('CREATE_SIGNAL_BASIC') 17 ) 18 @Subscription() 19 subscribeToUserMessagesBasic(message): UserMessage { 20 return { message }; 21 } 22 23 @Scope('ADMIN') 24 @Type(UserMessage) 25 @Subscribe( 26 withFilter( 27 (self: UserSubscriptionsController) => 28 self.pubsub.asyncIterator('CREATE_SIGNAL_WITH_FILTER'), 29 (payload, { id }, context) => { 30 console.log('Subscribed User: ', id, JSON.stringify(context)); 31 return true; 32 } 33 ) 34 ) 35 @Subscription({ 36 id: { 37 type: new GraphQLNonNull(GraphQLInt), 38 }, 39 }) 40 subscribeToUserMessagesWithFilter(message): UserMessage { 41 return { message }; 42 } 43}
1subscription { 2 subscribeToUserMessagesBasic { 3 message 4 } 5}
1subscription { 2 subscribeToUserMessagesWithFilter(id:1) { 3 message 4 } 5}
1import { Service } from '@rxdi/core'; 2import { IUserType } from '../../core/api-introspection'; 3 4@Service() 5export class AnotherService { 6 trimFirstLetter(username: string): string { 7 return username.charAt(1); 8 } 9 10 trimFirstLetterAsync(username): Promise<string> { 11 return Promise.resolve(this.trimFirstLetter(username)); 12 } 13 14 returnTrueAsync() { 15 return Promise.resolve(true); 16 } 17} 18 19@Service() 20export class UserService { 21 constructor() {} 22 23 findUser(id: number): IUserType { 24 return { 25 id: 1, 26 email: 'test@gmail.com', 27 type: 'ADMIN', 28 password: '123456', 29 name: 'Pesho', 30 settings: { 31 sidebar: true, 32 }, 33 }; 34 } 35 36 addUser(id: number): IUserType { 37 return { 38 id: 1, 39 email: 'test@gmail.com', 40 type: 'ADMIN', 41 password: '123456', 42 name: 'Pesho', 43 settings: { 44 sidebar: true, 45 }, 46 }; 47 } 48 49 deleteUser(id: number): IUserType { 50 return { 51 id: 1, 52 email: 'test@gmail.com', 53 type: 'ADMIN', 54 password: '123456', 55 name: 'Pesho', 56 settings: { 57 sidebar: true, 58 }, 59 }; 60 } 61 62 updateUser(id): IUserType { 63 return { 64 id: 1, 65 email: 'test@gmail.com', 66 type: 'ADMIN', 67 password: '123456', 68 name: 'Pesho', 69 settings: { 70 sidebar: true, 71 }, 72 }; 73 } 74}
1import { AppModule } from './app/app.module'; 2import { BootstrapFramework } from '@rxdi/core'; 3import { CoreModule } from '@gapi/core'; 4 5BootstrapFramework(AppModule, [CoreModule], { 6 init: false, 7 initOptions: { 8 effects: true, 9 plugins: true, 10 services: true, 11 controllers: true, 12 }, 13 logger: { 14 logging: true, 15 date: true, 16 exitHandler: true, 17 fileService: true, 18 hashes: true, 19 }, 20}).subscribe( 21 () => console.log('Started!'), 22 (e) => console.error(e) 23);
gapi start
1import { Module } from '@rxdi/core'; 2import { AuthPrivateService } from './services/auth/auth.service'; 3 4@Module({ 5 services: [AuthPrivateService], 6}) 7export class CoreModule {}
1import { Service } from '@rxdi/core'; 2import * as Boom from 'boom'; 3 4export interface UserInfo { 5 scope: ['ADMIN', 'USER']; 6 type: 'ADMIN' | 'USER'; 7 iat: number; 8} 9 10@Service() 11export class AuthPrivateService { 12 constructor() // private connectionHookService: ConnectionHookService // private authService: AuthService, 13 { 14 // this.connectionHookService.modifyHooks.onSubConnection = this.onSubConnection.bind(this); 15 // this.authService.modifyFunctions.validateToken = this.validateToken.bind(this); 16 } 17 18 onSubConnection(connectionParams): UserInfo { 19 if (connectionParams.token) { 20 return this.validateToken(connectionParams.token, 'Subscription'); 21 } else { 22 throw Boom.unauthorized(); 23 } 24 } 25 26 validateToken( 27 token: string, 28 requestType: 'Query' | 'Subscription' = 'Query' 29 ): UserInfo { 30 const user = <UserInfo>this.verifyToken(token); 31 user.type = user.scope[0]; 32 console.log(`${requestType} from: ${JSON.stringify(user)}`); 33 if (user) { 34 return user; 35 } else { 36 throw Boom.unauthorized(); 37 } 38 } 39 40 verifyToken(token: string): any { 41 // return this.authService.verifyToken(token); 42 return token; 43 } 44 45 signJWTtoken(tokenData: any): any { 46 // return this.authService.sign(tokenData); 47 return tokenData; 48 } 49 50 issueJWTToken(tokenData: any) { 51 // const jwtToken = this.authService.sign({ 52 // email: '', 53 // id: 1, 54 // scope: ['ADMIN', 'USER'] 55 // }); 56 // return jwtToken; 57 } 58 59 decryptPassword(password: string): any { 60 // return this.authService.decrypt(password); 61 return password; 62 } 63 64 encryptPassword(password: string): any { 65 // return this.authService.encrypt(password); 66 return password; 67 } 68}
1import { Module } from '@rxdi/core'; 2import { UserModule } from './user/user.module'; 3import { UserService } from './user/services/user.service'; 4import { CoreModule } from './core/core.module'; 5 6@Module({ 7 imports: [UserModule, CoreModule], 8}) 9export class AppModule {}
1import { Module, InjectionToken } from '@rxdi/core'; 2import { UserModule } from './user/user.module'; 3import { CoreModule } from './core/core.module'; 4 5class UserId { 6 id: number; 7} 8 9export const UserIdToken = new InjectionToken<UserId>('UserId'); 10 11@Module({ 12 imports: [UserModule, CoreModule], 13 services: [ 14 { 15 provide: 'UserId', 16 useValue: { id: 1 }, 17 }, 18 { 19 provide: UserIdToken, 20 useFactory: () => { 21 return { id: 1 }; 22 }, 23 }, 24 { 25 provide: UserIdToken, 26 useClass: UserId, 27 }, 28 ], 29}) 30export class AppModule {}
1@Controller() 2export class UserQueriesController { 3 constructor( 4 @Inject('UserId') private userId: { id: number }, // Value injection 5 @Inject(UserIdToken) private userId: UserId, // Token injection 6 @Inject(UserIdToken) private userId: UserId // Class injection 7 ) { 8 console.log(this.userId.id); 9 // Will print 1 10 } 11}
You can put also Observable as a value
1import { Module } from '@rxdi/core'; 2import { UserModule } from './user/user.module'; 3import { CoreModule } from './core/core.module'; 4import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 5 6@Module({ 7 imports: [UserModule, CoreModule], 8 services: [ 9 { 10 provide: 'Observable', 11 useValue: new BehaviorSubject(1), 12 }, 13 ], 14}) 15export class AppModule {}
Then inject this service somewhere in your application
1import { Controller } from '@rxdi/core'; 2import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 3import { PubSubService } from '@rxdi/graphql-pubsub'; 4 5@Controller() 6export class UserSubscriptionsController { 7 constructor( 8 @Inject('Observable') private observable: BehaviorSubject<number>, 9 private pubsub: PubSubService 10 ) { 11 this.observable.subscribe(() => 12 this.pubsub.publish( 13 'CREATE_SIGNAL_BASIC', 14 `Signal Published message: ${this.observable.getValue()}` 15 ) 16 ); 17 } 18}
Then you can put it inside another service and emit values
1import { Service, Inject } from '@rxdi/core'; 2import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 3import { TimerObservable } from 'rxjs/observable/TimerObservable'; 4 5@Service() 6export class UserService { 7 constructor( 8 @Inject('Observable') private observable: BehaviorSubject<number> 9 ) { 10 TimerObservable.create(0, 1000).subscribe((t) => this.observable.next(t)) 11 }
Cross reference circular dependency
1import { UserService } from './user.service'; 2 3@Service() 4export class AnotherService { 5 @Inject(() => UserService) private userService: UserService
1import { AnotherService } from './another.service'; 2 3@Service() 4export class UserService { 5 @Inject(() => AnotherService) private anotherService: AnotherService
You can see the subscription when you subscribe to basic chanel inside GraphiQL dev panel
1subscription { 2 subscribeToUserMessagesBasic { 3 message 4 } 5}
The result will be
1{ 2 "subscribeToUserMessagesBasic": { 3 "message": "Signal Published message: 495" 4 } 5}
1import { GapiObjectType, InjectType, Resolve, Type } from '@rxdi/graphql'; 2import { 3 GraphQLGapiObjectType, 4 GraphQLString, 5 GraphQLInt, 6 GraphQLList, 7 GraphQLBoolean, 8 GraphQLScalarType, 9} from 'graphql'; 10 11@GapiObjectType() 12export class UserSettingsType { 13 readonly id: number | GraphQLScalarType = GraphQLInt; 14 readonly color: string | GraphQLScalarType = GraphQLString; 15 readonly language: string | GraphQLScalarType = GraphQLString; 16 readonly sidebar: boolean | GraphQLScalarType = GraphQLBoolean; 17} 18 19@GapiObjectType() 20export class UserWalletSettingsType { 21 readonly type: string | GraphQLScalarType = GraphQLString; 22 readonly private: string | GraphQLScalarType = GraphQLString; 23 readonly security: string | GraphQLScalarType = GraphQLString; 24 readonly nested: UserSettingsType = InjectType(UserSettingsType); 25 readonly nested2: UserSettingsType = InjectType(UserSettingsType); 26 readonly nested3: UserSettingsType = InjectType(UserSettingsType); 27 28 // If you want you can change every value where you want with @Resolve decorator 29 @Resolve('type') 30 changeType(root, args, context) { 31 return root.type + ' new-type'; 32 } 33 34 // NOTE: you can name function methods as you wish can be Example3 for example important part is to define 'nested3' as a key to map method :) 35 @Resolve('nested3') 36 Example3(root, args, context) { 37 // CHANGE value of object type when returning 38 // UserSettingsType { 39 // "id": 1, 40 // "color": "black", 41 // "language": "en-US", 42 // "sidebar": true 43 // } 44 // root.nested3.id 45 // root.nested3.color 46 // root.nested3.language 47 // root.nested3.sidebar 48 return root.nested3; 49 } 50} 51 52@GapiObjectType() 53export class UserWalletType { 54 readonly id: number | GraphQLScalarType = GraphQLInt; 55 readonly address: string | GraphQLScalarType = GraphQLString; 56 readonly settings: string | UserWalletSettingsType = InjectType( 57 UserWalletSettingsType 58 ); 59} 60 61@GapiObjectType() 62export class UserType { 63 readonly id: number | GraphQLScalarType = GraphQLInt; 64 readonly email: string | GraphQLScalarType = GraphQLString; 65 readonly firstname: string | GraphQLScalarType = GraphQLString; 66 readonly lastname: string | GraphQLScalarType = GraphQLString; 67 readonly settings: string | UserSettingsType = InjectType( 68 UserWalletSettingsType 69 ); 70 readonly wallets: UserWalletType = new GraphQLList( 71 InjectType(UserWalletType) 72 ); 73}
1query { 2 findUser(id:1) { 3 id 4 email 5 firstname 6 lastname 7 settings { 8 id 9 color 10 language 11 sidebar 12 } 13 wallets { 14 id 15 address 16 settings { 17 type 18 private 19 security 20 nested { 21 id 22 color 23 language 24 sidebar 25 } 26 nested2 { 27 id 28 color 29 language 30 sidebar 31 } 32 nested3 { 33 id 34 color 35 language 36 sidebar 37 } 38 } 39 } 40 } 41} 42
1 2 findUser(id: number): UserType { 3 return { 4 id: 1, 5 email: "kristiqn.tachev@gmail.com", 6 firstname: "Kristiyan", 7 lastname: "Tachev", 8 settings: { 9 id: 1, 10 color: 'black', 11 language: 'en-US', 12 sidebar: true 13 }, 14 wallets: [{ 15 id: 1, address: 'dadadada', settings: { 16 type: "ethereum", 17 private: false, 18 security: "TWO-STEP", 19 nested: { 20 id: 1, 21 color: 'black', 22 language: 'en-US', 23 sidebar: true 24 }, 25 nested2: { 26 id: 1, 27 color: 'black', 28 language: 'en-US', 29 sidebar: true 30 }, 31 nested3: { 32 id: 1, 33 color: 'black', 34 language: 'en-US', 35 sidebar: true 36 }, 37 nested4: { 38 id: 1, 39 color: 'black', 40 language: 'en-US', 41 sidebar: true 42 }, 43 44 } 45 }] 46 }; 47 }
1{ 2 "data": { 3 "findUser": { 4 "id": 1, 5 "email": "kristiqn.tachev@gmail.com", 6 "firstname": "Kristiyan", 7 "lastname": "Tachev", 8 "settings": { 9 "id": 1, 10 "color": "black", 11 "language": "en-US", 12 "sidebar": true 13 }, 14 "wallets": [ 15 { 16 "id": 1, 17 "address": "dadadada", 18 "settings": { 19 "type": "ethereum new-type", 20 "private": "false", 21 "security": "TWO-STEP", 22 "nested": { 23 "id": 1, 24 "color": "black", 25 "language": "en-US", 26 "sidebar": true 27 }, 28 "nested2": { 29 "id": 1, 30 "color": "black", 31 "language": "en-US", 32 "sidebar": true 33 }, 34 "nested3": { 35 "id": 1, 36 "color": "black", 37 "language": "en-US", 38 "sidebar": true 39 } 40 } 41 } 42 ] 43 } 44 } 45}
All Gapi Decorators
@Query - Define Query object added above method inside @Controller
@Mutation - Define Mutation object added above method inside @Controller
@Subscription - Define Subscription object added above method inside @Controller
@Subscribe - It will be used with @Subscription Decorator and it takes PubSubIterator function @Subscribe(() => UserSubscriptionsController.pubsub.asyncIterator('CREATE_SIGNAL_BASIC')) can be used also withFilter
1 @Subscribe( 2 withFilter( 3 (self: UserSubscriptionsController) => self.pubsub.asyncIterator('CREATE_SIGNAL_WITH_FILTER'), 4 (payload, {id}, context) => { 5 console.log('User trying to subscribe: ', id, JSON.stringify(context)); 6 // if user passes your expression it will be subscribed to this subscription 7 return id !== context.id; 8 } 9 ) 10 ) 11 @Subscription() 12 subscribeToUserMessagesWithFilter(message): UserMessage { 13 return { message }; 14 }
@Public - Works with (@Query, @Mutation, Subscription) adds property "public = true" will make this particular resolver Public without authentication
@Scope - Can take arguments like what kind of User can use this Resolver @scope('ADMIN', 'USER', 'SALES')
@Type - Works with (@Query, @Mutation, Subscription) passing ObjectType class here for example UserType this is internally new GraphQLObjectType
@GapiObjectType - Internally is using new GraphQLObjectType() adding name of the class as a {name: constructor.name} can take {name: 'YourCustomName'} as argument also the same Object type can be used for generating new GraphQLInputObjectType when passing {input: true} used for Arguments
@Resolve - This is used internally inside GapiObjectType and it is related with modifying return result from GraphQL like in the following example
1@GapiObjectType() 2export class UserType { 3 id: number | GraphQLScalarType = GraphQLInt; 4 5 @Resolve('id') 6 getId?(root, payload, context) { 7 return 5; 8 } 9}
Important part is that getId? method needs to be OPTIONAL because it will be part of the Interface defined by the class UserType so everywhere if you use UserType it will ask you to add getId as a function but we just want to modify the return result from Schema with Resolve method decorator.
@Controller - It will define Controllers inside Gapi Application you can have as many as you wish controllers just when you are ready import them inside @Module({controllers: ...CONTROLLERS})
@Guard - this decorator will add Guards to particular resolvers can be used this way:
1import { Controller } from '@rxdi/core'; 2import { Type, Query, Guard } from '@rxdi/graphql'; 3import { UserService } from './services/user.service'; 4import { UserListType } from './types/user-list.type'; 5import { AdminOnly } from '../core/guards/admin-only.guard'; 6 7@Controller() 8export class UserQueriesController { 9 constructor(private userService: UserService) {} 10 11 @Type(UserListType) 12 @Guard(AdminOnly) 13 @Query() 14 listUsers() { 15 return this.userService.findAll(); 16 } 17}
Or you can apply them globaly to controller like this
1import { Query, GraphQLControllerOptions, InjectType } from '@rxdi/graphql'; 2import { UserService } from './services/user.service'; 3import { UserListType } from './types/user-list.type'; 4import { AdminOnly } from '../core/guards/admin-only.guard'; 5 6@Controller<GraphQLControllerOptions>({ 7 guards: [AdminOnly], 8 type: InjectType(UserListType), 9}) 10export class UserQueriesController { 11 constructor(private userService: UserService) {} 12 13 @Query() 14 listUsers() { 15 return this.userService.findAll(); 16 } 17}
The basic guard example:
1import { 2 Service, 3 CanActivateResolver, 4 GenericGapiResolversType, 5} from '@gapi/core'; 6import { ENUMS } from '../enums'; 7import { UserType } from '../../user/types/user.type'; 8 9@Service() 10export class AdminOnly implements CanActivateResolver { 11 canActivate( 12 context: UserType, 13 payload, 14 descriptor: GenericGapiResolversType 15 ) { 16 return context.type === 'ADMIN'; 17 } 18}
Valid use cases are:
1@Service() 2export class AdminOnly implements CanActivateResolver { 3 canActivate( 4 context: UserType, 5 payload, 6 descriptor: GenericGapiResolversType 7 ) { 8 return Observable.create((o) => o.next(true)); 9 10 return new Promise((r) => r(true)); 11 12 return Observable.create((o) => o.next(false)); 13 14 return new Promise((r) => r(false)); 15 if (context.type !== 'ADMIN') { 16 throw new Error('error'); 17 } 18 } 19}
If you try to mix @Guard decorator and apply globaly guards it will merge them so you will have both of the guards.
@Interceptor - This abstraction can be used to modify or just log result before you process it to the client can be used like this:
Logging interceptor
1import { 2 InterceptResolver, 3 Service, 4 GenericGapiResolversType, 5} from '@gapi/core'; 6import { Observable } from 'rxjs'; 7import { tap } from 'rxjs/operators'; 8import { UserType } from '../../user/types/user.type'; 9 10@Service() 11export class LoggerInterceptor implements InterceptResolver { 12 intercept( 13 chainable$: Observable<any>, 14 context: UserType, 15 payload, 16 descriptor: GenericGapiResolversType 17 ) { 18 console.log('Before...'); 19 const now = Date.now(); 20 return chainable$.pipe( 21 tap(() => console.log(`After... ${Date.now() - now}ms`)) 22 ); 23 } 24}
Then you can attach it inside Query, Mutation, Subscription decorators
1 @Interceptor(LoggerInterceptor) 2 @Query({ 3 id: { 4 type: new GraphQLNonNull(GraphQLString) 5 } 6 }) 7 findUser(root, { id }): Promise<User> { 8 return this.userService.findUserById(id); 9 } 10
You can also modify result returned
Modify interceptor
1import { 2 InterceptResolver, 3 Service, 4 GenericGapiResolversType, 5} from '@gapi/core'; 6import { Observable } from 'rxjs'; 7import { tap } from 'rxjs/operators'; 8import { UserType } from '../../user/types/user.type'; 9 10@Service() 11export class ModifyInterceptor implements InterceptResolver { 12 intercept( 13 chainable$: Observable<any>, 14 context: UserType, 15 payload, 16 descriptor: GenericGapiResolversType 17 ) { 18 console.log('Before...'); 19 const now = Date.now(); 20 return chainable$.pipe( 21 map((res) => { 22 console.log(`After... ${Date.now() - now}ms`); 23 return res; 24 }) 25 ); 26 } 27}
@Module - This is the starting point of the application when we bootstrap our module inside root/src/main.ts.Also can be used to create another Modules which can be imported inside AppModule {imports: ...IMPORTS}
@Service - Passed above class, this Decorator will insert metadata related with this class and all Dependencies Injected inside constructor() so when application starts you will get a Singleton of many Services if they are not Factory Services(Will explain how to create Factory in next release).So you can use single instance of a Service injected everywhere inside your application.
@Inject - It will inject Service with specific NAME for example when using InjectionToken('my-name') you can do something like
1constructor( 2 @Inject('my-name') private name: string; 3) {}
@Injector - Use this very carefully! It will Inject Services before application is fully loaded used to load Instance of a class before the real load of application it is used only inside GapiObjectType because Types are the first thing that will be loaded inside Gapi Application so we need our Services on Processing Decorators which is when the application loads.If you can use Dependency Injection internally provided.
@InjectType - This is BETA decorator for now is used without Decorator sign @ example:
1@GapiObjectType() 2export class UserType { 3 readonly id: number | GraphQLScalarType = GraphQLInt; 4 readonly settings: string | UserSettings = InjectType(UserSettingsType); 5}
In future releases it will be used as follows:
1@GapiObjectType() 2export class UserType { 3 readonly id: number | GraphQLScalarType = GraphQLInt; 4 @InjectType(UserSettingsType) readonly settings: string; 5}
TODO: Better documentation...
Enjoy ! :)
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
binaries present in source code
Details
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
Found 0/30 approved changesets -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
no SAST tool detected
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
project is not fuzzed
Details
Reason
149 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-07-14
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More