├── .husky ├── pre-commit └── commit-msg ├── tests ├── IUser.ts ├── schema.cds ├── middlewares │ ├── handler.middleware.ts │ └── user.checker.ts ├── services │ └── greeter.service.ts ├── tsconfig.json ├── entities.ts ├── services.cds ├── handlers │ ├── service.handler.ts │ └── greeter.handler.ts └── server.ts ├── .yarnrc ├── src ├── types │ ├── HandlerType.ts │ ├── IExecContext.ts │ ├── IRegisterOptions.ts │ ├── ICdsRoutingHandlerOptions.ts │ ├── ParamType.ts │ ├── ODataOperation.ts │ ├── MiddlewareRuntime.ts │ ├── IUserChecker.ts │ └── ICdsMiddleware.ts ├── decorators │ ├── class │ │ ├── options │ │ │ └── IMiddlewareOptions.ts │ │ ├── UserChecker.ts │ │ ├── Handler.ts │ │ ├── Use.ts │ │ └── Middleware.ts │ ├── param │ │ ├── User.ts │ │ ├── Data.ts │ │ ├── Locale.ts │ │ ├── Next.ts │ │ ├── Entities.ts │ │ ├── Jwt.ts │ │ ├── Req.ts │ │ ├── Srv.ts │ │ ├── ParamObj.ts │ │ └── Param.ts │ └── method │ │ ├── Reject.ts │ │ ├── Action.ts │ │ ├── Func.ts │ │ ├── Read.ts │ │ ├── Delete.ts │ │ ├── Update.ts │ │ └── Create.ts ├── metadata │ ├── args │ │ ├── IUserCheckerMetadataArgs.ts │ │ ├── IUseMetadataArgs.ts │ │ ├── IHandlerMetadataArgs.ts │ │ ├── IMiddlewareMetadataArgs.ts │ │ ├── IRejectMetadataArgs.ts │ │ ├── IParamMetadataArgs.ts │ │ └── IActionMetadataArgs.ts │ ├── base │ │ ├── CloudSdkReplacement.ts │ │ └── Executer.ts │ ├── RejectMetadata.ts │ ├── UserCheckerMetadata.ts │ ├── HandlerMetadata.ts │ ├── ParamMetadata.ts │ ├── MiddlewareMetadata.ts │ └── ActionMetadata.ts ├── container.ts ├── index.ts ├── metadata-builder │ ├── MetadataBuilder.ts │ └── MetadataArgsStorage.ts └── CDSHandler.ts ├── .prettierrc ├── .cdsrc.json ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── commitlint.config.js ├── tsconfig.json ├── LICENSE ├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .eslintrc.json ├── .gitignore ├── package.json ├── cds-routing-handlers.postman_collection.json └── README.md /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /tests/IUser.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | username: string; 3 | service: string; 4 | } 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "" 5 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | strict-ssl false 4 | "@sap:registry" "https://npm.sap.com" -------------------------------------------------------------------------------- /src/types/HandlerType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Handler type. 3 | * 4 | * @enum {number} 5 | */ 6 | export enum HandlerType { 7 | Before, 8 | On, 9 | After, 10 | } 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": false, 6 | "quote-props": "as-needed", 7 | "printWidth": 120, 8 | "endOfLine": "auto" 9 | } 10 | -------------------------------------------------------------------------------- /src/types/IExecContext.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Execution context. 3 | * 4 | * @export 5 | * @interface IExecContext 6 | */ 7 | export interface IExecContext { 8 | srv: any; 9 | req: any; 10 | next: Function | undefined; 11 | e?: any[] | any; 12 | } 13 | -------------------------------------------------------------------------------- /src/types/IRegisterOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Register options. 3 | * 4 | * @export 5 | * @interface IRegisterOptions 6 | */ 7 | export interface IRegisterOptions { 8 | handler: Function[]; 9 | middlewares?: Function[]; 10 | userChecker?: Function; 11 | } 12 | -------------------------------------------------------------------------------- /src/types/ICdsRoutingHandlerOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * CDS routing handler options. 3 | * 4 | * @export 5 | * @interface CdsRoutingHandlerOptions 6 | */ 7 | export interface ICdsRoutingHandlerOptions { 8 | handler: Function[] | string[]; 9 | middlewares?: Function[]; 10 | userChecker?: Function; 11 | } 12 | -------------------------------------------------------------------------------- /src/types/ParamType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parameter type. 3 | * 4 | * @export 5 | * @enum {number} 6 | */ 7 | export enum ParamType { 8 | Srv, 9 | Req, 10 | Data, 11 | ParamObj, 12 | Param, 13 | Jwt, 14 | Entities, 15 | Next, 16 | Locale, 17 | User, 18 | } 19 | -------------------------------------------------------------------------------- /tests/schema.cds: -------------------------------------------------------------------------------- 1 | using { managed } from '@sap/cds/common'; 2 | 3 | namespace test; 4 | 5 | entity TestEntity: managed { 6 | key Id: UUID @readonly default 0; 7 | Name: String; 8 | }; 9 | 10 | entity DraftEnabledEntity: managed { 11 | key Id: UUID @readonly default 0; 12 | Name: String; 13 | }; -------------------------------------------------------------------------------- /src/types/ODataOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Operation type. 3 | * 4 | * @export 5 | * @enum {string} 6 | */ 7 | export enum ODataOperation { 8 | Create = "CREATE", 9 | Read = "READ", 10 | Update = "UPDATE", 11 | Delete = "DELETE", 12 | Function = "FUNC", 13 | Action = "ACTION", 14 | } 15 | -------------------------------------------------------------------------------- /.cdsrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "target": ".", 4 | "tasks": [ 5 | { 6 | "for": "node-cf", 7 | "src": "srv", 8 | "options": { 9 | "model": ["srv"] 10 | } 11 | } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/decorators/class/options/IMiddlewareOptions.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareRuntime } from "../../../types/MiddlewareRuntime"; 2 | 3 | /** 4 | * Middleware decorator options. 5 | * 6 | * @export 7 | * @interface IMiddlewareOptions 8 | */ 9 | export interface IMiddlewareOptions { 10 | global?: boolean; 11 | priority?: number; 12 | runtime?: MiddlewareRuntime; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/MiddlewareRuntime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware runtime. 3 | * 4 | * @export 5 | * @enum {number} 6 | */ 7 | export enum MiddlewareRuntime { 8 | Normal, 9 | /** 10 | * @deprecated: not available since @sap/cds > 3.31.2 11 | */ 12 | BeforeDefaults, 13 | /** 14 | * @deprecated: not available since @sap/cds > 3.31.2 15 | */ 16 | AfterDefaults, 17 | } 18 | -------------------------------------------------------------------------------- /tests/middlewares/handler.middleware.ts: -------------------------------------------------------------------------------- 1 | import { ICdsMiddleware, Middleware, Req, Jwt } from "../../lib"; 2 | 3 | @Middleware() 4 | export class HandlerMiddleware implements ICdsMiddleware { 5 | public async use(@Req() req: any, @Jwt() jwt: string): Promise { 6 | console.log("I am a handler middleware"); 7 | console.log(`My jwt is ${jwt ? jwt : "empty"}`); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": ["javascript", "typescript"], 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.formatOnSave": true, 5 | "files.exclude": { 6 | "**/node_modules": true 7 | }, 8 | "[javascript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "eslint.packageManager": "yarn" 12 | } 13 | -------------------------------------------------------------------------------- /src/metadata/args/IUserCheckerMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * User checker metadata arguments. 3 | * 4 | * @export 5 | * @interface IUserCheckerMetadataArgs 6 | */ 7 | export interface IUserCheckerMetadataArgs { 8 | /** 9 | * Target: JS function for the handler class. 10 | * 11 | * @type {Function} 12 | * @memberof IUserCheckerMetadataArgs 13 | */ 14 | target: Function; 15 | } 16 | -------------------------------------------------------------------------------- /src/decorators/class/UserChecker.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | 3 | /** 4 | * User checker decorator. 5 | * 6 | * @export 7 | * @returns {ClassDecorator} 8 | */ 9 | export function UserChecker(): ClassDecorator { 10 | return (target: Function) => { 11 | getMetadataArgsStorage().addUserCheckerMetadata({ 12 | target: target, 13 | }); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /tests/services/greeter.service.ts: -------------------------------------------------------------------------------- 1 | import { Service } from "typedi"; 2 | 3 | @Service() 4 | export class GreeterService { 5 | public async greet(name: string): Promise { 6 | return new Promise((resolve, reject) => { 7 | setTimeout(() => { 8 | const result = "Hello, " + name; 9 | resolve(result); 10 | }, 2000); 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build:all", 9 | "group": "build", 10 | "problemMatcher": [], 11 | "label": "Build All" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tests/middlewares/user.checker.ts: -------------------------------------------------------------------------------- 1 | import { UserChecker, IUserChecker, Srv } from "../../lib"; 2 | import { IUser } from "../IUser"; 3 | 4 | @UserChecker() 5 | export class UserCheckerImpl implements IUserChecker { 6 | public check(@Srv() srv: any): IUser { 7 | console.log("I am UserChecker"); 8 | 9 | return { 10 | username: "mrbandler", 11 | service: "", 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/types/IUserChecker.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * User checker interface. 3 | * 4 | * @export 5 | * @interface IUserChecker 6 | */ 7 | export interface IUserChecker { 8 | /** 9 | * Will be called when the user checker is used. 10 | * 11 | * @param {...any[]} args Arguments, can be used to inject like in any other handler 12 | * @returns {Promise} 13 | * @memberof IUserChecker 14 | */ 15 | check(...args: any[]): any; 16 | } 17 | -------------------------------------------------------------------------------- /src/decorators/class/Handler.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | 3 | /** 4 | * Handler decorator. 5 | * 6 | * @export 7 | * @param {string} entity Entity for which the decorator is used 8 | * @returns {ClassDecorator} 9 | */ 10 | export function Handler(entity?: string): ClassDecorator { 11 | return (target: Function) => { 12 | getMetadataArgsStorage().addHandlerMetadata({ target, entity }); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/types/ICdsMiddleware.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware interface. 3 | * 4 | * @export 5 | * @interface IMiddleware 6 | */ 7 | export interface ICdsMiddleware { 8 | /** 9 | * Will be called when the middleware is used. 10 | * 11 | * @param {...any[]} args Arguments, can be used to inject like in any other handler 12 | * @returns {Promise} 13 | * @memberof ICdsMiddleware 14 | */ 15 | use(...args: any[]): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "subject-case": [2, "always", "sentence-case"], 5 | "type-enum": [ 6 | 2, 7 | "always", 8 | ["feat", "fix", "docs", "style", "refactor", "test", "revert", "chore", "merge", "release", "ci", "wiki"], 9 | ], 10 | }, 11 | ignores: [message => /^((?:Merge|Merged){1}).*/.test(message)], 12 | defaultIgnores: true, 13 | }; 14 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "outDir": "../srv", 7 | "rootDir": ".", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "forceConsistentCasingInFileNames": true, 13 | 14 | "skipLibCheck": true 15 | }, 16 | 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /src/decorators/class/Use.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | 3 | /** 4 | * Middleware usage decorator. 5 | * 6 | * @export 7 | * @param {...Function[]} middlewares Middlewares to use 8 | * @returns {Function} 9 | */ 10 | export function Use(...middlewares: Function[]): ClassDecorator { 11 | return (target: Function) => { 12 | middlewares.forEach(m => { 13 | getMetadataArgsStorage().addUseMetadata({ target: target, middleware: m }); 14 | }); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "outDir": "./lib", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "forceConsistentCasingInFileNames": true 14 | }, 15 | "exclude": ["./tests", "node_modules", "./lib"] 16 | } 17 | -------------------------------------------------------------------------------- /src/metadata/args/IUseMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware usage metadata arguments. 3 | * 4 | * @export 5 | * @interface IUseMetadataArgs 6 | */ 7 | export interface IUseMetadataArgs { 8 | /** 9 | * Object class of this "use". 10 | * 11 | * @type {Function} 12 | * @memberof IUseMetadataArgs 13 | */ 14 | target: Function; 15 | 16 | /** 17 | * Middleware to be executed for this "use". 18 | * 19 | * @type {Function} 20 | * @memberof IUseMetadataArgs 21 | */ 22 | middleware: Function; 23 | } 24 | -------------------------------------------------------------------------------- /src/metadata/args/IHandlerMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Handler metadata arguments. 3 | * 4 | * @export 5 | * @interface IHandlerMetadataArgs 6 | */ 7 | export interface IHandlerMetadataArgs { 8 | /** 9 | * Target: JS function for the handler class. 10 | * 11 | * @type {Function} 12 | * @memberof IHandlerMetadataArgs 13 | */ 14 | target: Function; 15 | 16 | /** 17 | * Entity on which the handler acts. 18 | * 19 | * @type {string} 20 | * @memberof IHandlerMetadataArgs 21 | */ 22 | entity?: string; 23 | } 24 | -------------------------------------------------------------------------------- /src/decorators/param/User.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * User parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function User(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.User, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/decorators/param/Data.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * Request data parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function Data(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.Data, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/decorators/param/Locale.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * Locale parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function Locale(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.Locale, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/decorators/param/Next.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * Next handler parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function Next(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.Next, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/decorators/param/Entities.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * Entities parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function Entities(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.Entities, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/decorators/param/Jwt.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * JWT parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function Jwt(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.Jwt, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/decorators/param/Req.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * Request parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function Req(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.Req, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/decorators/param/Srv.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * Service parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function Srv(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.Srv, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/decorators/param/ParamObj.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * Param object parameter decorator. 6 | * 7 | * @export 8 | * @returns {ParameterDecorator} 9 | */ 10 | export function ParamObj(): ParameterDecorator { 11 | return (target: Object, key: string | symbol, index: number) => { 12 | getMetadataArgsStorage().addParamMetadata({ 13 | object: target, 14 | method: key as string, 15 | index: index, 16 | type: ParamType.ParamObj, 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Tests", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}/srv/server.js", 13 | "args": ["--inspect"], 14 | "outFiles": ["${workspaceFolder}/srv/**/*.js"] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/decorators/param/Param.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ParamType } from "../../types/ParamType"; 3 | 4 | /** 5 | * Param parameter decorator. 6 | * 7 | * @export 8 | * @param {string} name Name of the parameter 9 | * @returns {ParameterDecorator} 10 | */ 11 | export function Param(name: string): ParameterDecorator { 12 | return (target: Object, key: string | symbol, index: number) => { 13 | getMetadataArgsStorage().addParamMetadata({ 14 | object: target, 15 | method: key as string, 16 | index: index, 17 | type: ParamType.Param, 18 | name: name, 19 | }); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/decorators/method/Reject.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | 3 | /** 4 | * Reject handler decorator. 5 | * 6 | * @export 7 | * @param {number} code HTTP response code 8 | * @param {string} message Response message 9 | * @returns {MethodDecorator} 10 | */ 11 | export function OnReject(code: number, message: string, appendErrorMessage: boolean = false): MethodDecorator { 12 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 13 | getMetadataArgsStorage().addRejectMetadata({ 14 | target: target.constructor, 15 | method: key as string, 16 | code: code, 17 | message: message, 18 | appendErrorMessage: appendErrorMessage, 19 | }); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/decorators/method/Action.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { HandlerType } from "../../types/HandlerType"; 3 | import { ODataOperation } from "../../types/ODataOperation"; 4 | 5 | /** 6 | * Action handler decorator. 7 | * 8 | * @export 9 | * @param {string} name Name of the action 10 | * @returns {MethodDecorator} 11 | */ 12 | export function Action(name: string): MethodDecorator { 13 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 14 | getMetadataArgsStorage().addActionMetadata({ 15 | target: target.constructor, 16 | method: key as string, 17 | handler: HandlerType.On, 18 | operation: ODataOperation.Action, 19 | functionImportName: name, 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/decorators/method/Func.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { HandlerType } from "../../types/HandlerType"; 3 | import { ODataOperation } from "../../types/ODataOperation"; 4 | 5 | /** 6 | * Function handler decorator. 7 | * 8 | * @export 9 | * @param {string} name Name of the function 10 | * @returns {MethodDecorator} 11 | */ 12 | export function Func(name: string): MethodDecorator { 13 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 14 | getMetadataArgsStorage().addActionMetadata({ 15 | target: target.constructor, 16 | method: key as string, 17 | handler: HandlerType.On, 18 | operation: ODataOperation.Function, 19 | functionImportName: name, 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /tests/entities.ts: -------------------------------------------------------------------------------- 1 | export namespace TestService { 2 | export enum ActionGreeter { 3 | name = "greeter", 4 | paramTitle = "title", 5 | paramName = "name", 6 | } 7 | 8 | export interface IActionGreeterParams { 9 | title: string; 10 | name: string; 11 | } 12 | 13 | export enum FuncHello { 14 | name = "hello", 15 | paramName = "name", 16 | } 17 | 18 | export interface IFuncHelloParams { 19 | name: string; 20 | } 21 | 22 | export interface IGreeter { 23 | Id: string; 24 | Name: string; 25 | Message: string; 26 | } 27 | 28 | export enum Entity { 29 | Greeter = "TestService.Greeter", 30 | } 31 | 32 | export enum SanitizedEntity { 33 | Greeter = "Greeter", 34 | } 35 | } 36 | 37 | export enum Entity {} 38 | 39 | export enum SanitizedEntity {} 40 | -------------------------------------------------------------------------------- /tests/services.cds: -------------------------------------------------------------------------------- 1 | using {test as my} from './schema'; 2 | 3 | @path: 'testService' 4 | service TestService { 5 | function hello(name: String) returns String; 6 | action greeter(title: String, name: String) returns GreeterReturn; 7 | 8 | type GreeterReturn { 9 | title: String; 10 | name: String; 11 | } 12 | 13 | entity Greeter { 14 | key Id: String; 15 | Name: String; 16 | Message: String; 17 | } 18 | 19 | entity TestEntity as projection on my.TestEntity; 20 | 21 | entity DraftEnabledEntity as projection on my.DraftEnabledEntity; 22 | } 23 | 24 | annotate TestService.DraftEnabledEntity with @odata.draft.enabled; 25 | 26 | annotate TestService.DraftEnabledEntity with @UI : { 27 | LineItem : [ 28 | { 29 | Value : Id, 30 | ![@UI.Importance] : #High 31 | }, 32 | { Value : Name } 33 | ] 34 | }; -------------------------------------------------------------------------------- /src/decorators/class/Middleware.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { IMiddlewareOptions } from "./options/IMiddlewareOptions"; 3 | import { MiddlewareRuntime } from "../../types/MiddlewareRuntime"; 4 | 5 | /** 6 | * Middleware decorator. 7 | * 8 | * @export 9 | * @param {IMiddlewareOptions} [options] Middleware options, should only be used for global middlewares 10 | * @returns {ClassDecorator} 11 | */ 12 | export function Middleware(options?: IMiddlewareOptions): ClassDecorator { 13 | return (target: Function) => { 14 | const global = options ? options.global || false : false; 15 | const priority = options ? options.priority || 99 : 99; 16 | const runtime = options ? options.runtime || MiddlewareRuntime.Normal : MiddlewareRuntime.Normal; 17 | getMetadataArgsStorage().addMiddlewareMetadata({ 18 | target: target, 19 | global: global, 20 | priority: priority, 21 | runtime: runtime, 22 | }); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /tests/handlers/service.handler.ts: -------------------------------------------------------------------------------- 1 | import { Handler, Func, Action, Param, ParamObj, Req, Srv, Jwt } from "../../lib"; 2 | import { GreeterService } from "../services/greeter.service"; 3 | import { Inject } from "typedi"; 4 | 5 | interface IParams { 6 | title: string; 7 | name: string; 8 | } 9 | 10 | @Handler() 11 | export class ServiceHandler { 12 | @Inject(() => GreeterService) 13 | private greeterService!: GreeterService; 14 | 15 | @Func("hello") 16 | public async hello(@Req() req: any, @Param("name") name: string, @Jwt() jwt: string): Promise { 17 | console.log("Function hello"); 18 | return await this.greeterService.greet(name); 19 | } 20 | 21 | @Action("greeter") 22 | public async greeter( 23 | @Req() req: any, 24 | @Srv() srv: any, 25 | @Param("title") title: string, 26 | @Param("name") name: string, 27 | @ParamObj() params: IParams 28 | ): Promise { 29 | console.log("Action greeter"); 30 | return { 31 | title, 32 | name, 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/metadata/args/IMiddlewareMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareRuntime } from "../../types/MiddlewareRuntime"; 2 | 3 | /** 4 | * Middleware metadata arguments. 5 | * 6 | * @export 7 | * @interface IMiddlewareMetadataArgs 8 | */ 9 | export interface IMiddlewareMetadataArgs { 10 | /** 11 | * Target: JS function for the handler class. 12 | * 13 | * @type {Function} 14 | * @memberof IMiddlewareMetadataArgs 15 | */ 16 | target: Function; 17 | 18 | /** 19 | * Indicates if this middleware is global, thous applied to all routes. 20 | * 21 | * @type {boolean} 22 | * @memberof IMiddlewareMetadataArgs 23 | */ 24 | global: boolean; 25 | 26 | /** 27 | * Execution priority of the middleware. 28 | * 29 | * @type {number} 30 | * @memberof IMiddlewareMetadataArgs 31 | */ 32 | priority: number; 33 | 34 | /** 35 | * Middleware runtime. 36 | * 37 | * @type {MiddlewareRuntime} 38 | * @memberof IMiddlewareMetadataArgs 39 | */ 40 | runtime: MiddlewareRuntime; 41 | } 42 | -------------------------------------------------------------------------------- /src/metadata/args/IRejectMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Reject metadata arguments. 3 | * 4 | * @export 5 | * @interface IRejectMetadataArgs 6 | */ 7 | export interface IRejectMetadataArgs { 8 | /** 9 | * Target: JS function of the handler class method. 10 | * 11 | * @type {Function} 12 | * @memberof IRejectMetadataArgs 13 | */ 14 | target: Function; 15 | 16 | /** 17 | * Method name. 18 | * 19 | * @type {string} 20 | * @memberof IRejectMetadataArgs 21 | */ 22 | method: string; 23 | 24 | /** 25 | * HTTP Response code. 26 | * 27 | * @type {number} 28 | * @memberof IRejectMetadataArgs 29 | */ 30 | code: number; 31 | 32 | /** 33 | * Response message. 34 | * 35 | * @type {string} 36 | * @memberof IRejectMetadataArgs 37 | */ 38 | message: string; 39 | 40 | /** 41 | * Flag, whether the JS error message should be appended. 42 | * 43 | * @type {boolean} 44 | * @memberof IRejectMetadataArgs 45 | */ 46 | appendErrorMessage: boolean; 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 mrbandler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/metadata/args/IParamMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | import { ParamType } from "../../types/ParamType"; 2 | 3 | /** 4 | * Parameter metadata arguments. 5 | * 6 | * @export 7 | * @interface IParamMetadataArgs 8 | */ 9 | export interface IParamMetadataArgs { 10 | /** 11 | * Object on which the method's parameter this parameter is attached. 12 | * 13 | * @type {*} 14 | * @memberof IParamMetadataArgs 15 | */ 16 | object: any; 17 | 18 | /** 19 | * Method name. 20 | * 21 | * @type {string} 22 | * @memberof IParamMetadataArgs 23 | */ 24 | method: string; 25 | 26 | /** 27 | * Parameter index. 28 | * 29 | * @type {number} 30 | * @memberof IParamMetadataArgs 31 | */ 32 | index: number; 33 | 34 | /** 35 | * Parameter type. 36 | * 37 | * @type {ParamType} 38 | * @memberof IParamMetadataArgs 39 | */ 40 | type: ParamType; 41 | 42 | /** 43 | * Name of the parameter used for @Param("name") decorator. 44 | * 45 | * @type {string} 46 | * @memberof IParamMetadataArgs 47 | */ 48 | name?: string; 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [master, develop] 6 | pull_request: 7 | branches: [master, develop] 8 | # Manual run 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Clone repo 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup node environment 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: 12 22 | registry-url: https://registry.npmjs.org/ 23 | 24 | - name: Download cache 25 | uses: actions/cache@v2.0.0 26 | with: 27 | key: cds-routing-handlers-cache 28 | path: node_modules 29 | 30 | - name: Install and build 31 | run: | 32 | yarn install 33 | yarn build 34 | 35 | - name: Upload build artifacts 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: cds-routing-handlers-artifacts 39 | path: lib 40 | -------------------------------------------------------------------------------- /src/metadata/args/IActionMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | import { HandlerType } from "../../types/HandlerType"; 2 | import { ODataOperation } from "../../types/ODataOperation"; 3 | 4 | /** 5 | * Action metadata arguments. 6 | * 7 | * @export 8 | * @interface ICRUDMetadataArgs 9 | */ 10 | export interface IActionMetadataArgs { 11 | /** 12 | * Target: JS function of the handler class method. 13 | * 14 | * @type {Function} 15 | * @memberof IActionMetadataArgs 16 | */ 17 | target: Function; 18 | 19 | /** 20 | * Method name. 21 | * 22 | * @type {string} 23 | * @memberof IActionMetadataArgs 24 | */ 25 | method: string; 26 | 27 | /** 28 | * Handler type. 29 | * 30 | * @type {HandlerType} 31 | * @memberof IActionMetadataArgs 32 | */ 33 | handler: HandlerType; 34 | 35 | /** 36 | * Operation type. 37 | * 38 | * @type {ODataOperation} 39 | * @memberof IActionMetadataArgs 40 | */ 41 | operation: ODataOperation; 42 | 43 | /** 44 | * Function import name. 45 | * 46 | * @type {string} 47 | * @memberof IActionMetadataArgs 48 | */ 49 | functionImportName?: string; 50 | } 51 | -------------------------------------------------------------------------------- /src/metadata/base/CloudSdkReplacement.ts: -------------------------------------------------------------------------------- 1 | import { IncomingMessage } from "http"; 2 | 3 | export function retrieveJwt(req: IncomingMessage): string | undefined { 4 | const header = authHeader(req); 5 | if (validateAuthHeader(header)) { 6 | return header!.split(" ")[1]; 7 | } 8 | } 9 | 10 | function authHeader(req: IncomingMessage): string | undefined { 11 | const entries = Object.entries(req.headers).find(([key]) => key.toLowerCase() === "authorization"); 12 | if (entries) { 13 | const header = entries[1]; 14 | 15 | // Header could be a list of headers 16 | return Array.isArray(header) ? header[0] : header; 17 | } 18 | return undefined; 19 | } 20 | 21 | function validateAuthHeader(header: string | undefined): boolean { 22 | if (typeof header === "undefined") { 23 | console.warn("Authorization header not set."); 24 | return false; 25 | } 26 | 27 | const [authType, token] = header.split(" "); 28 | 29 | if (typeof token === "undefined") { 30 | console.warn("Token in auth header missing."); 31 | return false; 32 | } 33 | 34 | if (authType.toLowerCase() !== "bearer") { 35 | console.warn("Authorization type is not Bearer."); 36 | return false; 37 | } 38 | 39 | return true; 40 | } 41 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:prettier/recommended"], 3 | "plugins": ["prettier", "@typescript-eslint"], 4 | "env": { 5 | "browser": true, 6 | "es6": true 7 | }, 8 | "overrides": [ 9 | { 10 | "files": "**/*.ts", 11 | "env": { 12 | "browser": false 13 | }, 14 | "parser": "@typescript-eslint/parser", 15 | "parserOptions": { 16 | "ecmaVersion": 6, 17 | "sourceType": "module", 18 | "ecmaFeatures": { 19 | "modules": true 20 | } 21 | }, 22 | "plugins": ["@typescript-eslint"], 23 | "rules": { 24 | "@typescript-eslint/naming-convention": [ 25 | "error", 26 | { 27 | "selector": "interface", 28 | "format": ["PascalCase"], 29 | "custom": { 30 | "regex": "^I[A-Z]", 31 | "match": true 32 | } 33 | } 34 | ] 35 | } 36 | }, 37 | { 38 | "files": "**/*.js", 39 | "rules": { 40 | "quote-props": ["warn", "as-needed"] 41 | } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /tests/server.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import * as handler from "../lib/index"; 3 | import cds from "@sap/cds"; 4 | import odatav2proxy from "@sap/cds-odata-v2-adapter-proxy"; 5 | import express from "express"; 6 | import { Container } from "typedi"; 7 | import { HandlerMiddleware } from "./middlewares/handler.middleware"; 8 | import { UserCheckerImpl } from "./middlewares/user.checker"; 9 | 10 | class Main { 11 | public static run(): void { 12 | const server = express(); 13 | const port = process.env.PORT ? parseInt(process.env.PORT) : 3001; 14 | const genPath = __dirname + "/gen/csn.json"; 15 | 16 | handler.useContainer(Container); 17 | const hdl = handler.createCombinedHandler({ 18 | handler: [__dirname + "/handlers/**/*.js"], 19 | middlewares: [HandlerMiddleware], 20 | userChecker: UserCheckerImpl, 21 | }); 22 | cds.serve(genPath) 23 | .at("odata") 24 | .in(server) 25 | .with((srv: any) => { 26 | hdl(srv); 27 | }) 28 | .catch((error: any) => { 29 | console.log("Startup error: ", error); 30 | process.exit(1); 31 | }); 32 | 33 | server.use( 34 | odatav2proxy({ 35 | port: port, 36 | path: "v2", 37 | model: genPath, 38 | services: { 39 | "/odata": "TestService", 40 | }, 41 | }) 42 | ); 43 | 44 | server.listen(port, async () => { 45 | console.log("Server running on port %d", port); 46 | }); 47 | } 48 | } 49 | 50 | Main.run(); 51 | -------------------------------------------------------------------------------- /tests/handlers/greeter.handler.ts: -------------------------------------------------------------------------------- 1 | import { Handler, Req, OnRead, OnReject, AfterRead, Entities, Next, Data, Use, User, Jwt } from "../../lib"; 2 | import { HandlerMiddleware } from "../middlewares/handler.middleware"; 3 | import { IUser } from "../IUser"; 4 | 5 | interface IData { 6 | Id: string; 7 | Name: string; 8 | Message: string; 9 | } 10 | 11 | @Handler("Greeter") 12 | @Use(HandlerMiddleware) 13 | export class GreeterHandler { 14 | @OnRead() 15 | @OnReject(500, "Nope", true) 16 | public async read( 17 | @Req() req: any, 18 | @Next() next: Function, 19 | @Data() data: IData, 20 | @User() user: IUser, 21 | @Jwt() jwt: string 22 | ): Promise { 23 | console.log("I am the ReadMiddleware"); 24 | console.log(`My jwt is ${jwt ? jwt : "empty"}`); 25 | 26 | return [ 27 | { 28 | Id: "8HEXDIG-4HEXDIG-4HEXDIG-4HEXDIG-12HEXDIG", 29 | Name: "Nicola", 30 | Message: "Nicola Message", 31 | }, 32 | { 33 | Id: "8HEXDIG-4HEXDIG-4HEXDIG-4HEXDIG-12HEXDIG", 34 | Name: "Mario", 35 | Message: "Mario Message", 36 | }, 37 | { 38 | Id: "8HEXDIG-4HEXDIG-4HEXDIG-4HEXDIG-12HEXDIG", 39 | Name: "Simon", 40 | Message: "Simon Message", 41 | }, 42 | ]; 43 | } 44 | 45 | @AfterRead() 46 | public async afterRead(@Req() req: any, @Entities() entities: IData[]): Promise { 47 | return entities.map((e) => { 48 | e.Message = "After read was here!"; 49 | return e; 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/decorators/method/Read.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { HandlerType } from "../../types/HandlerType"; 3 | import { ODataOperation } from "../../types/ODataOperation"; 4 | 5 | /** 6 | * Before read handler decorator. 7 | * 8 | * @export 9 | * @returns {MethodDecorator} 10 | */ 11 | export function BeforeRead(): MethodDecorator { 12 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 13 | getMetadataArgsStorage().addActionMetadata({ 14 | target: target.constructor, 15 | method: key as string, 16 | handler: HandlerType.Before, 17 | operation: ODataOperation.Read, 18 | }); 19 | }; 20 | } 21 | 22 | /** 23 | * On read handler decorator. 24 | * 25 | * @export 26 | * @returns {MethodDecorator} 27 | */ 28 | export function OnRead(): MethodDecorator { 29 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 30 | getMetadataArgsStorage().addActionMetadata({ 31 | target: target.constructor, 32 | method: key as string, 33 | handler: HandlerType.On, 34 | operation: ODataOperation.Read, 35 | }); 36 | }; 37 | } 38 | 39 | /** 40 | * After read handler decorator. 41 | * 42 | * @export 43 | * @returns {MethodDecorator} 44 | */ 45 | export function AfterRead(): MethodDecorator { 46 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 47 | getMetadataArgsStorage().addActionMetadata({ 48 | target: target.constructor, 49 | method: key as string, 50 | handler: HandlerType.After, 51 | operation: ODataOperation.Read, 52 | }); 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/decorators/method/Delete.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { ODataOperation } from "../../types/ODataOperation"; 3 | import { HandlerType } from "../../types/HandlerType"; 4 | 5 | /** 6 | * Before delete handler decorator. 7 | * 8 | * @export 9 | * @returns {MethodDecorator} 10 | */ 11 | export function BeforeDelete(): MethodDecorator { 12 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 13 | getMetadataArgsStorage().addActionMetadata({ 14 | target: target.constructor, 15 | method: key as string, 16 | handler: HandlerType.Before, 17 | operation: ODataOperation.Delete, 18 | }); 19 | }; 20 | } 21 | 22 | /** 23 | * On delete handler decorator. 24 | * 25 | * @export 26 | * @returns {MethodDecorator} 27 | */ 28 | export function OnDelete(): MethodDecorator { 29 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 30 | getMetadataArgsStorage().addActionMetadata({ 31 | target: target.constructor, 32 | method: key as string, 33 | handler: HandlerType.On, 34 | operation: ODataOperation.Delete, 35 | }); 36 | }; 37 | } 38 | 39 | /** 40 | * After delete handler decorator. 41 | * 42 | * @export 43 | * @returns {MethodDecorator} 44 | */ 45 | export function AfterDelete(): MethodDecorator { 46 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 47 | getMetadataArgsStorage().addActionMetadata({ 48 | target: target.constructor, 49 | method: key as string, 50 | handler: HandlerType.After, 51 | operation: ODataOperation.Delete, 52 | }); 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/decorators/method/Update.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { HandlerType } from "../../types/HandlerType"; 3 | import { ODataOperation } from "../../types/ODataOperation"; 4 | 5 | /** 6 | * Before update handler decorator. 7 | * 8 | * @export 9 | * @returns {MethodDecorator} 10 | */ 11 | export function BeforeUpdate(): MethodDecorator { 12 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 13 | getMetadataArgsStorage().addActionMetadata({ 14 | target: target.constructor, 15 | method: key as string, 16 | handler: HandlerType.Before, 17 | operation: ODataOperation.Update, 18 | }); 19 | }; 20 | } 21 | 22 | /** 23 | * On update handler decorator. 24 | * 25 | * @export 26 | * @returns {MethodDecorator} 27 | */ 28 | export function OnUpdate(): MethodDecorator { 29 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 30 | getMetadataArgsStorage().addActionMetadata({ 31 | target: target.constructor, 32 | method: key as string, 33 | handler: HandlerType.On, 34 | operation: ODataOperation.Update, 35 | }); 36 | }; 37 | } 38 | 39 | /** 40 | * After update handler decorator. 41 | * 42 | * @export 43 | * @returns {MethodDecorator} 44 | */ 45 | export function AfterUpdate(): MethodDecorator { 46 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 47 | getMetadataArgsStorage().addActionMetadata({ 48 | target: target.constructor, 49 | method: key as string, 50 | handler: HandlerType.After, 51 | operation: ODataOperation.Update, 52 | }); 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/decorators/method/Create.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../../index"; 2 | import { HandlerType } from "../../types/HandlerType"; 3 | import { ODataOperation } from "../../types/ODataOperation"; 4 | 5 | /** 6 | * Before create handler decorator. 7 | * 8 | * @export 9 | * @returns {MethodDecorator} 10 | */ 11 | export function BeforeCreate(): MethodDecorator { 12 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 13 | getMetadataArgsStorage().addActionMetadata({ 14 | target: target.constructor, 15 | method: key.toString(), 16 | handler: HandlerType.Before, 17 | operation: ODataOperation.Create, 18 | }); 19 | }; 20 | } 21 | 22 | /** 23 | * On create handler decorator. 24 | * 25 | * @export 26 | * @returns {MethodDecorator} 27 | */ 28 | export function OnCreate(): MethodDecorator { 29 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 30 | getMetadataArgsStorage().addActionMetadata({ 31 | target: target.constructor, 32 | method: key.toString(), 33 | handler: HandlerType.On, 34 | operation: ODataOperation.Create, 35 | }); 36 | }; 37 | } 38 | 39 | /** 40 | * After create handler decorator. 41 | * 42 | * @export 43 | * @returns {MethodDecorator} 44 | */ 45 | export function AfterCreate(): MethodDecorator { 46 | return (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { 47 | getMetadataArgsStorage().addActionMetadata({ 48 | target: target.constructor, 49 | method: key.toString(), 50 | handler: HandlerType.After, 51 | operation: ODataOperation.Create, 52 | }); 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Clone repo 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup node environment 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: 12 19 | registry-url: https://registry.npmjs.org/ 20 | 21 | - name: Download cache 22 | uses: actions/cache@v2.0.0 23 | with: 24 | key: cds-routing-handlers-cache 25 | path: node_modules 26 | 27 | - name: Install and build 28 | run: | 29 | yarn install 30 | yarn build 31 | 32 | - name: Upload build artifacts 33 | uses: actions/upload-artifact@v2 34 | with: 35 | name: cds-routing-handlers-artifacts 36 | path: lib 37 | 38 | publish: 39 | needs: build 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Clone repo 43 | uses: actions/checkout@v2 44 | 45 | - name: Download build artifacts 46 | uses: actions/download-artifact@v2 47 | with: 48 | name: cds-routing-handlers-artifacts 49 | path: lib 50 | 51 | - name: Setup node environment 52 | uses: actions/setup-node@v2 53 | with: 54 | node-version: 12 55 | registry-url: https://registry.npmjs.org/ 56 | 57 | - name: Install dev dependencies (needed for publish) 58 | run: npm install 59 | 60 | - name: Publish to npm 61 | uses: JS-DevTools/npm-publish@v1 62 | with: 63 | token: ${{ secrets.NPM_TOKEN }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=node,visualstudiocode 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | 77 | # next.js build output 78 | .next 79 | 80 | # nuxt.js build output 81 | .nuxt 82 | 83 | # react / gatsby 84 | public/ 85 | 86 | # vuepress build output 87 | .vuepress/dist 88 | 89 | # Serverless directories 90 | .serverless/ 91 | 92 | # FuseBox cache 93 | .fusebox/ 94 | 95 | # DynamoDB Local files 96 | .dynamodb/ 97 | 98 | ### VisualStudioCode ### 99 | .vscode/* 100 | !.vscode/settings.json 101 | !.vscode/tasks.json 102 | !.vscode/launch.json 103 | !.vscode/extensions.json 104 | 105 | ### VisualStudioCode Patch ### 106 | # Ignore all local history of files 107 | .history 108 | 109 | # End of https://www.gitignore.io/api/node,visualstudiocode 110 | 111 | # Custom 112 | /lib 113 | srv/ -------------------------------------------------------------------------------- /src/metadata/RejectMetadata.ts: -------------------------------------------------------------------------------- 1 | import { IRejectMetadataArgs } from "./args/IRejectMetadataArgs"; 2 | 3 | /** 4 | * Rejection metadata. 5 | * 6 | * @export 7 | * @class RejectMetadata 8 | */ 9 | export class RejectMetadata { 10 | /** 11 | * Target: JS function of the handler class method. 12 | * 13 | * @type {Function} 14 | * @memberof IRejectMetadataArgs 15 | */ 16 | private _target: Function; 17 | 18 | /** 19 | * Method name. 20 | * 21 | * @type {string} 22 | * @memberof IRejectMetadataArgs 23 | */ 24 | private _method: string; 25 | 26 | /** 27 | * HTTP Response code. 28 | * 29 | * @type {number} 30 | * @memberof IRejectMetadataArgs 31 | */ 32 | private _code: number; 33 | 34 | /** 35 | * Response message. 36 | * 37 | * @type {string} 38 | * @memberof IRejectMetadataArgs 39 | */ 40 | private _message: string; 41 | 42 | /** 43 | * Flag, whether the JS error message should be appended. 44 | * 45 | * @type {boolean} 46 | * @memberof RejectMetadata 47 | */ 48 | private _appendErrorMessage: boolean; 49 | 50 | /** 51 | * HTTP Response code. 52 | * 53 | * @readonly 54 | * @type {number} 55 | * @memberof RejectMetadata 56 | */ 57 | public get code(): number { 58 | return this._code; 59 | } 60 | 61 | /** 62 | * Response message. 63 | * 64 | * @readonly 65 | * @type {string} 66 | * @memberof RejectMetadata 67 | */ 68 | public get message(): string { 69 | return this._message; 70 | } 71 | 72 | /** 73 | * Flag, whether the JS error message should be appended. 74 | * 75 | * @readonly 76 | * @type {boolean} 77 | * @memberof RejectMetadata 78 | */ 79 | public get appendErrorMessage(): boolean { 80 | return this._appendErrorMessage; 81 | } 82 | 83 | /** 84 | * Default constructor. 85 | * 86 | * @param {IRejectMetadataArgs} args Metadata arguments 87 | * @memberof RejectMetadata 88 | */ 89 | constructor(args: IRejectMetadataArgs) { 90 | this._target = args.target; 91 | this._method = args.method; 92 | this._code = args.code; 93 | this._message = args.message; 94 | this._appendErrorMessage = args.appendErrorMessage; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/metadata/UserCheckerMetadata.ts: -------------------------------------------------------------------------------- 1 | import { ParamMetadata } from "./ParamMetadata"; 2 | import { getFromContainer } from "../index"; 3 | import { Executor } from "./base/Executer"; 4 | import { IExecContext } from "../types/IExecContext"; 5 | import { IUserChecker } from "../types/IUserChecker"; 6 | import { IUserCheckerMetadataArgs } from "./args/IUserCheckerMetadataArgs"; 7 | 8 | /** 9 | * Middleware metadata and executer. 10 | * 11 | * @export 12 | * @class UserCheckerMetadata 13 | */ 14 | export class UserCheckerMetadata extends Executor { 15 | /** 16 | * Target: Typescript class. 17 | * 18 | * @type {Function} 19 | * @memberof UserCheckerMetadata 20 | */ 21 | private _target: Function; 22 | 23 | /** 24 | * Middleware parameters. 25 | * 26 | * @type {ParamMetadata[]} 27 | * @memberof UserCheckerMetadata 28 | */ 29 | private _params: ParamMetadata[] = []; 30 | 31 | /** 32 | * Target: Typescript class. 33 | * 34 | * @readonly 35 | * @type {Function} 36 | * @memberof UserCheckerMetadata 37 | */ 38 | public get target(): Function { 39 | return this._target; 40 | } 41 | 42 | /** 43 | * Middleware parameters. 44 | * 45 | * @memberof UserCheckerMetadata 46 | */ 47 | public set params(value: ParamMetadata[]) { 48 | this._params = value; 49 | } 50 | 51 | /** 52 | * Returns the instance of the middleware target. 53 | * 54 | * @readonly 55 | * @type {IUserChecker} 56 | * @memberof UserCheckerMetadata 57 | */ 58 | get instance(): IUserChecker { 59 | return getFromContainer(this._target); 60 | } 61 | 62 | /** 63 | * Default constructor. 64 | * 65 | * @param {IUserCheckerMetadataArgs} args User checker metadata arguments 66 | * @memberof UserCheckerMetadata 67 | */ 68 | constructor(args: IUserCheckerMetadataArgs) { 69 | super(); 70 | 71 | this._target = args.target; 72 | } 73 | 74 | /** 75 | * Executes the middleware. 76 | * 77 | * @param {IExecContext} context Execution context 78 | * @returns {*} Execution result 79 | * @memberof MiddlewareMetadata 80 | */ 81 | public exec(context: IExecContext): any { 82 | const instance = this.instance; 83 | const params = this.buildParams(this._params, context); 84 | 85 | if (instance["check"]) { 86 | return instance.check.apply(instance, params); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/metadata/HandlerMetadata.ts: -------------------------------------------------------------------------------- 1 | import { IHandlerMetadataArgs } from "./args/IHandlerMetadataArgs"; 2 | import { ActionMetadata } from "./ActionMetadata"; 3 | import { getFromContainer } from "../container"; 4 | 5 | /** 6 | * Handler metadata. 7 | * 8 | * @export 9 | * @class HandlerMetadata 10 | */ 11 | export class HandlerMetadata { 12 | /** 13 | * Target: Typescript class. 14 | * 15 | * @type {Function} 16 | * @memberof HandlerMetadata 17 | */ 18 | private _target: Function; 19 | 20 | /** 21 | * Entity for which the handler is registerd. 22 | * 23 | * @type {string} 24 | * @memberof HandlerMetadata 25 | */ 26 | private _entity?: string; 27 | 28 | /** 29 | * Actions metadata. 30 | * 31 | * @type {ActionMetadata[]} 32 | * @memberof HandlerMetadata 33 | */ 34 | private _actions: ActionMetadata[] = []; 35 | 36 | /** 37 | * Target: Typescript class. 38 | * 39 | * @readonly 40 | * @type {Function} 41 | * @memberof HandlerMetadata 42 | */ 43 | public get target(): Function { 44 | return this._target; 45 | } 46 | 47 | /** 48 | * Entity for which the handler is registerd. 49 | * 50 | * @readonly 51 | * @type {(string | undefined)} 52 | * @memberof HandlerMetadata 53 | */ 54 | public get entity(): string | undefined { 55 | return this._entity; 56 | } 57 | 58 | /** 59 | * Returns a instance of the handler. 60 | * 61 | * @readonly 62 | * @type {*} Instance of the handler class 63 | * @memberof HandlerMetadata 64 | */ 65 | public get instance(): any { 66 | return getFromContainer(this.target); 67 | } 68 | 69 | /** 70 | * Actions metadata. 71 | * 72 | * @type {ActionMetadata[]} 73 | * @memberof HandlerMetadata 74 | */ 75 | public get actions(): ActionMetadata[] { 76 | return this._actions; 77 | } 78 | 79 | /** 80 | * Actions metadata. 81 | * 82 | * @memberof HandlerMetadata 83 | */ 84 | public set actions(value: ActionMetadata[]) { 85 | this._actions = value; 86 | } 87 | 88 | /** 89 | * Default constructor. 90 | * 91 | * @param {IHandlerMetadataArgs} args Metadata arguments 92 | * @memberof HandlerMetadata 93 | */ 94 | constructor(args: IHandlerMetadataArgs) { 95 | this._target = args.target; 96 | this._entity = args.entity; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/container.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Container options. 3 | * 4 | * @export 5 | * @interface IUseContainerOptions 6 | */ 7 | export interface IUseContainerOptions { 8 | /** 9 | * If set to true, then default container will be used in the case if given container haven't returned anything. 10 | */ 11 | fallback?: boolean; 12 | 13 | /** 14 | * If set to true, then default container will be used in the case if given container thrown an exception. 15 | */ 16 | fallbackOnErrors?: boolean; 17 | } 18 | 19 | /** 20 | * Container to be used by this library for inversion control. If container was not implicitly set then by default 21 | * container simply creates a new instance of the given class. 22 | */ 23 | const defaultContainer: { get(someClass: { new (...args: any[]): T } | Function): T } = new (class { 24 | private instances: { type: Function; object: any }[] = []; 25 | get(someClass: { new (...args: any[]): T }): T { 26 | let instance = this.instances.find(instance => instance.type === someClass); 27 | if (!instance) { 28 | instance = { type: someClass, object: new someClass() }; 29 | this.instances.push(instance); 30 | } 31 | 32 | return instance.object; 33 | } 34 | })(); 35 | 36 | let userContainer: { get(someClass: { new (...args: any[]): T } | Function): T }; 37 | let userContainerOptions: IUseContainerOptions | undefined; 38 | 39 | /** 40 | * Sets container to be used by this library. 41 | * 42 | * @export 43 | * @param {{ get(someClass: any): any }} iocContainer 44 | * @param {IUseContainerOptions} [options] 45 | */ 46 | export function useContainer(iocContainer: { get(someClass: any): any }, options?: IUseContainerOptions) { 47 | userContainer = iocContainer; 48 | userContainerOptions = options; 49 | } 50 | 51 | /** 52 | * Gets the IOC container used by this library. 53 | * 54 | * @export 55 | * @template T 56 | * @param {({ new (...args: any[]): T } | Function)} someClass 57 | * @returns {T} 58 | */ 59 | export function getFromContainer(someClass: { new (...args: any[]): T } | Function): T { 60 | if (userContainer) { 61 | try { 62 | const instance = userContainer.get(someClass); 63 | if (instance) return instance; 64 | 65 | if (!userContainerOptions || !userContainerOptions.fallback) return instance; 66 | } catch (error) { 67 | if (!userContainerOptions || !userContainerOptions.fallbackOnErrors) throw error; 68 | } 69 | } 70 | return defaultContainer.get(someClass); 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cds-routing-handlers", 3 | "version": "3.0.7", 4 | "description": "Package to route and implement CDS handlers via a class based approach in Typescript.", 5 | "main": "lib/index.js", 6 | "repository": "https://github.com/mrbandler/cds-routing-handlers", 7 | "author": "mrbandler ", 8 | "license": "MIT", 9 | "readme": "README.md", 10 | "bugs": { 11 | "url": "https://github.com/mrbandler/cds-routing-handlers/issues" 12 | }, 13 | "keywords": [ 14 | "typescript", 15 | "controller", 16 | "handler", 17 | "cds", 18 | "sap", 19 | "cap", 20 | "cloud", 21 | "application", 22 | "programming" 23 | ], 24 | "files": [ 25 | "lib" 26 | ], 27 | "engines": { 28 | "node": ">=12.0.0" 29 | }, 30 | "scripts": { 31 | "build": "tsc", 32 | "build:all": "rimraf ./lib && yarn build && yarn build:test", 33 | "build:test": "tsc --project ./tests/tsconfig.json && copyfiles -f ./tests/services.cds ./tests/schema.cds ./srv && cds build/all --clean", 34 | "start:test": "yarn build:test && node --inspect ./srv/server.js", 35 | "prepare": "husky install" 36 | }, 37 | "devDependencies": { 38 | "@commitlint/cli": "^16.0.1", 39 | "@commitlint/config-conventional": "^16.0.0", 40 | "@sap/cds": "^5.7.4", 41 | "@sap/cds-odata-v2-adapter-proxy": "^1.8.4", 42 | "@types/express": "^4.17.2", 43 | "@types/node": "^12.12.11", 44 | "@typescript-eslint/eslint-plugin": "^5.8.1", 45 | "@typescript-eslint/parser": "^5.8.1", 46 | "copyfiles": "^2.1.1", 47 | "eslint": "^8.5.0", 48 | "eslint-config-prettier": "^8.3.0", 49 | "eslint-plugin-prettier": "^4.0.0", 50 | "express": "^4.17.1", 51 | "husky": "^7.0.0", 52 | "lint-staged": "^9.4.3", 53 | "prettier": "^2.5.1", 54 | "rimraf": "^3.0.0", 55 | "typedi": "0.8.0", 56 | "typescript": "^4.5.4" 57 | }, 58 | "dependencies": { 59 | "glob": "^7.2.0", 60 | "reflect-metadata": "^0.1.13" 61 | }, 62 | "lint-staged": { 63 | "*.{js,ts,css,json,md,yml}": [ 64 | "prettier --write" 65 | ], 66 | "*.{js,ts}": [ 67 | "eslint --fix" 68 | ] 69 | }, 70 | "cds": { 71 | "requires": { 72 | "db": { 73 | "kind": "hana", 74 | "model": [ 75 | "srv" 76 | ] 77 | } 78 | }, 79 | "odata": { 80 | "version": "v4" 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/metadata/ParamMetadata.ts: -------------------------------------------------------------------------------- 1 | import { ParamType } from "../types/ParamType"; 2 | import { IParamMetadataArgs } from "./args/IParamMetadataArgs"; 3 | import { throws } from "assert"; 4 | 5 | /** 6 | * Parameter metadata. 7 | * 8 | * @export 9 | * @class ParamMetadata 10 | */ 11 | export class ParamMetadata { 12 | /** 13 | * Class object. 14 | * 15 | * @type {*} 16 | * @memberof ParamMetadata 17 | */ 18 | private _object: any; 19 | 20 | /** 21 | * Target: JS function that represents the Typescript class. 22 | * 23 | * @type {Function} 24 | * @memberof ParamMetadata 25 | */ 26 | private _target: Function; 27 | 28 | /** 29 | * Method the parameter belongs to. 30 | * 31 | * @type {string} 32 | * @memberof ParamMetadata 33 | */ 34 | private _method: string; 35 | 36 | /** 37 | * Index of the parameter. 38 | * 39 | * @type {number} 40 | * @memberof ParamMetadata 41 | */ 42 | private _index: number; 43 | 44 | /** 45 | * Parameter type. 46 | * 47 | * @type {ParamType} 48 | * @memberof ParamMetadata 49 | */ 50 | private _type: ParamType; 51 | 52 | /** 53 | * Name of the parameter. 54 | * 55 | * @type {string} 56 | * @memberof ParamMetadata 57 | */ 58 | private _name?: string; 59 | 60 | /** 61 | * Target type of the parameter. 62 | * 63 | * @type {*} 64 | * @memberof ParamMetadata 65 | */ 66 | private _targetType?: any; 67 | 68 | /** 69 | * Name of the target class. 70 | * 71 | * @type {string} 72 | * @memberof ParamMetadata 73 | */ 74 | private _targetName: string = ""; 75 | 76 | /** 77 | * Flag, whether the target is a object. 78 | * 79 | * @type {boolean} 80 | * @memberof ParamMetadata 81 | */ 82 | private _isTargetObject: boolean = false; 83 | 84 | /** 85 | * Parameter index. 86 | * 87 | * @readonly 88 | * @type {number} 89 | * @memberof ParamMetadata 90 | */ 91 | public get index(): number { 92 | return this._index; 93 | } 94 | 95 | /** 96 | * Parameter type. 97 | * 98 | * @readonly 99 | * @type {ParamType} 100 | * @memberof ParamMetadata 101 | */ 102 | public get type(): ParamType { 103 | return this._type; 104 | } 105 | 106 | /** 107 | * Name of the parameter. 108 | * 109 | * @readonly 110 | * @type {(string | undefined)} 111 | * @memberof ParamMetadata 112 | */ 113 | public get name(): string | undefined { 114 | return this._name; 115 | } 116 | 117 | /** 118 | * Default constructor. 119 | * 120 | * @param {ActionMetadata} actionMetadata Action metadata 121 | * @param {IParamMetadataArgs} args Parameter arguments 122 | * @memberof ParamMetadata 123 | */ 124 | constructor(args: IParamMetadataArgs) { 125 | this._target = args.object.constructor; 126 | this._method = args.method; 127 | this._index = args.index; 128 | this._type = args.type; 129 | this._name = args.name; 130 | 131 | const paramTypes = (Reflect as any).getMetadata("design:paramtypes", args.object, args.method); 132 | if (paramTypes !== undefined) { 133 | this._targetType = paramTypes[args.index]; 134 | } 135 | 136 | if (this._targetType) { 137 | if (this._targetType instanceof Function && this._targetType.name) { 138 | this._targetName = this._targetType.name.toLowerCase(); 139 | } else if (typeof this._targetType === "string") { 140 | this._targetName = this._targetType.toLowerCase(); 141 | } 142 | 143 | this._isTargetObject = this._targetType instanceof Function || this._targetType.toLowerCase() === "object"; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/metadata/base/Executer.ts: -------------------------------------------------------------------------------- 1 | import { ParamMetadata } from "../ParamMetadata"; 2 | import { IExecContext } from "../../types/IExecContext"; 3 | import { ParamType } from "../../types/ParamType"; 4 | import { UserCheckerMetadata } from "../UserCheckerMetadata"; 5 | import { retrieveJwt } from "./CloudSdkReplacement"; 6 | 7 | /** 8 | * Abstract executer class. 9 | * 10 | * Contains utils to build the necessary paramter list. 11 | * 12 | * @export 13 | * @abstract 14 | * @class Executer 15 | */ 16 | export abstract class Executor { 17 | /** 18 | * Abstract exec method, to be implemented in the child class. 19 | * 20 | * @protected 21 | * @abstract 22 | * @param {IExecContext} context Execution context to act on 23 | * @returns {*} Result 24 | * @memberof Executer 25 | */ 26 | public abstract exec(context: IExecContext): any; 27 | 28 | /** 29 | * Builds a paramter list out of all definied parameter decorators. 30 | * 31 | * @protected 32 | * @param {ParamMetadata[]} params Parameter metadata to build the list for 33 | * @param {IExecContext} context Execution context 34 | * @returns 35 | * @memberof Executer 36 | */ 37 | protected buildParams(params: ParamMetadata[], context: IExecContext, userChecker?: UserCheckerMetadata): any[] { 38 | const sortedParams = params.sort((a, b) => { 39 | if (a.index > b.index) return 1; 40 | if (b.index > a.index) return -1; 41 | 42 | return 0; 43 | }); 44 | 45 | return sortedParams.map((param) => { 46 | switch (param.type) { 47 | case ParamType.Srv: 48 | return context.srv; 49 | case ParamType.Req: 50 | return context.req; 51 | case ParamType.Data: 52 | return context.req.data; 53 | case ParamType.ParamObj: 54 | return context.req.data; 55 | case ParamType.Param: 56 | return this.namedParam(param, context.req); 57 | case ParamType.Jwt: 58 | return this.extractJwt(context); 59 | case ParamType.Entities: 60 | return context.e; 61 | case ParamType.Next: 62 | return context.next; 63 | case ParamType.Locale: 64 | return context.req.user.locale; 65 | case ParamType.User: 66 | return userChecker ? userChecker.exec(context) : undefined; 67 | } 68 | }); 69 | } 70 | 71 | /** 72 | * Retrieves the JWT token from a given request context object. 73 | * Try to get token with some fallback strategys. 74 | * @private 75 | * @param {*} req Request object to read the JWT token from 76 | * @returns {(string | undefined)} JWT token 77 | * @memberof ActionMetadata 78 | */ 79 | private extractJwt(context: any): string | undefined { 80 | let token; 81 | 82 | try { 83 | token = retrieveJwt(context.req._.req); 84 | } catch (error) { 85 | // silence 86 | } 87 | try { 88 | if (!token) token = retrieveJwt(context.req); 89 | } catch (error) { 90 | // silence 91 | } 92 | try { 93 | if (!token) token = context.req.attr.token || ""; 94 | } catch (error) { 95 | // silence 96 | } 97 | 98 | return token; 99 | } 100 | 101 | /** 102 | * Reads a parameter from the request data object. 103 | * 104 | * @protected 105 | * @param {ParamMetadata} param Parameter to read from the object 106 | * @param {*} req Incoming CDS request 107 | * @returns {*} Builds parameter 108 | * @memberof Executer 109 | */ 110 | protected namedParam(param: ParamMetadata, req: any): any { 111 | let result = undefined; 112 | 113 | if (param.name) { 114 | result = req.data[param.name]; 115 | } 116 | 117 | return result; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { CDSHandler } from "./CDSHandler"; 3 | import { MetadataArgsStorage } from "./metadata-builder/MetadataArgsStorage"; 4 | import { ICdsRoutingHandlerOptions } from "./types/ICdsRoutingHandlerOptions"; 5 | 6 | export * from "./container"; 7 | export * from "./decorators/class/options/IMiddlewareOptions"; 8 | export * from "./decorators/class/Handler"; 9 | export * from "./decorators/class/Use"; 10 | export * from "./decorators/class/Middleware"; 11 | export * from "./decorators/class/UserChecker"; 12 | export * from "./decorators/method/Create"; 13 | export * from "./decorators/method/Read"; 14 | export * from "./decorators/method/Update"; 15 | export * from "./decorators/method/Delete"; 16 | export * from "./decorators/method/Reject"; 17 | export * from "./decorators/method/Func"; 18 | export * from "./decorators/method/Action"; 19 | export * from "./decorators/param/Srv"; 20 | export * from "./decorators/param/Req"; 21 | export * from "./decorators/param/ParamObj"; 22 | export * from "./decorators/param/Param"; 23 | export * from "./decorators/param/Jwt"; 24 | export * from "./decorators/param/Entities"; 25 | export * from "./decorators/param/Data"; 26 | export * from "./decorators/param/Next"; 27 | export * from "./decorators/param/Locale"; 28 | export * from "./decorators/param/User"; 29 | export * from "./types/ODataOperation"; 30 | export * from "./types/ICdsMiddleware"; 31 | export * from "./types/IUserChecker"; 32 | export * from "./types/ICdsRoutingHandlerOptions"; 33 | export * from "./types/MiddlewareRuntime"; 34 | 35 | /** 36 | * Returns the metadata arguments storage. 37 | * 38 | * @export 39 | * @returns {MetadataArgsStorage} Metadata arguments storage 40 | */ 41 | export function getMetadataArgsStorage(): MetadataArgsStorage { 42 | if (!(global as any).cdsHandlersMetadataArgsStorage) 43 | (global as any).cdsHandlersMetadataArgsStorage = new MetadataArgsStorage(); 44 | 45 | return (global as any).cdsHandlersMetadataArgsStorage; 46 | } 47 | 48 | /** 49 | * Imports decorated classes from directories. 50 | * 51 | * @param {string[]} directories Directories to search in 52 | * @param {string} [formats=[".js", ".ts"]] Formats to import classes from 53 | * @returns {Function[]} Imported classes 54 | */ 55 | function importClassesFromDirectories(directories: string[], formats = [".js", ".ts"]): Function[] { 56 | const loadFileClasses = function(exported: any, allLoaded: Function[]) { 57 | if (exported instanceof Function) { 58 | allLoaded.push(exported); 59 | } else if (exported instanceof Array) { 60 | exported.forEach((i: any) => loadFileClasses(i, allLoaded)); 61 | } else if (exported instanceof Object || typeof exported === "object") { 62 | Object.keys(exported).forEach(key => loadFileClasses(exported[key], allLoaded)); 63 | } 64 | 65 | return allLoaded; 66 | }; 67 | 68 | const allFiles = directories.reduce((allDirs, dir) => { 69 | return allDirs.concat(require("glob").sync(path.normalize(dir))); 70 | }, [] as string[]); 71 | 72 | const dirs = allFiles 73 | .filter(file => { 74 | const dtsExtension = file.substring(file.length - 5, file.length); 75 | return formats.indexOf(path.extname(file)) !== -1 && dtsExtension !== ".d.ts"; 76 | }) 77 | .map(file => { 78 | return require(file); 79 | }); 80 | 81 | return loadFileClasses(dirs, []); 82 | } 83 | 84 | /** 85 | * Create combined handler. 86 | * 87 | * @export 88 | * @param {(Function[] | string[])} handlers Handlers; either classes directly or the directories where the handlers reside 89 | * @returns {(srv: any) => void} Function that is used to register all endpoints 90 | */ 91 | export function createCombinedHandler(options: ICdsRoutingHandlerOptions): (srv: any) => void { 92 | return (srv: any) => { 93 | if (!(srv.before && srv.on && srv.after)) { 94 | console.error("Service (srv) parameter does not seem to be a CDS service implementation"); 95 | 96 | return; 97 | } 98 | 99 | let handlerClasses: Function[]; 100 | if (options.handler && options.handler.length) { 101 | handlerClasses = (options.handler as any[]).filter(controller => controller instanceof Function); 102 | const handlerDirs = (options.handler as any[]).filter(controller => typeof controller === "string"); 103 | handlerClasses.push(...importClassesFromDirectories(handlerDirs)); 104 | 105 | CDSHandler.register(srv, { 106 | handler: handlerClasses, 107 | middlewares: options.middlewares, 108 | userChecker: options.userChecker, 109 | }); 110 | } 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /src/metadata/MiddlewareMetadata.ts: -------------------------------------------------------------------------------- 1 | import { ParamMetadata } from "./ParamMetadata"; 2 | import { getFromContainer, MiddlewareRuntime } from "../index"; 3 | import { ICdsMiddleware } from "../types/ICdsMiddleware"; 4 | import { IMiddlewareMetadataArgs } from "./args/IMiddlewareMetadataArgs"; 5 | import { Executor } from "./base/Executer"; 6 | import { IExecContext } from "../types/IExecContext"; 7 | import { UserCheckerMetadata } from "./UserCheckerMetadata"; 8 | 9 | /** 10 | * Middleware metadata and executer. 11 | * 12 | * @export 13 | * @class MiddlewareMetadata 14 | */ 15 | export class MiddlewareMetadata extends Executor { 16 | /** 17 | * Target: Typescript class. 18 | * 19 | * @type {Function} 20 | * @memberof MiddlewareMetadata 21 | */ 22 | private _target: Function; 23 | 24 | /** 25 | * Flag, whether this middleware is a global one. 26 | * 27 | * @type {boolean} 28 | * @memberof MiddlewareMetadata 29 | */ 30 | private _global: boolean; 31 | 32 | /** 33 | * Priority of a global middleware. 34 | * 35 | * @type {number} 36 | * @memberof MiddlewareMetadata 37 | */ 38 | private _priority: number; 39 | 40 | /** 41 | * Middleware runtime. 42 | * 43 | * @private 44 | * @type {MiddlewareRuntime} 45 | * @memberof MiddlewareMetadata 46 | */ 47 | private _runtime: MiddlewareRuntime; 48 | 49 | /** 50 | * Entities on which to apply the middleware. 51 | * 52 | * @type {string} 53 | * @memberof MiddlewareMetadata 54 | */ 55 | private _entities?: string[]; 56 | 57 | /** 58 | * Middleware parameters. 59 | * 60 | * @type {ParamMetadata[]} 61 | * @memberof MiddlewareMetadata 62 | */ 63 | private _params: ParamMetadata[] = []; 64 | 65 | /** 66 | * User checker. 67 | * 68 | * @private 69 | * @type {UserCheckerMetadata} 70 | * @memberof MiddlewareMetadata 71 | */ 72 | private _userChecker?: UserCheckerMetadata; 73 | 74 | /** 75 | * Target: Typescript class. 76 | * 77 | * @readonly 78 | * @type {Function} 79 | * @memberof MiddlewareMetadata 80 | */ 81 | public get target(): Function { 82 | return this._target; 83 | } 84 | 85 | /** 86 | * Flag, whether this middleware is a global one. 87 | * 88 | * @readonly 89 | * @type {boolean} 90 | * @memberof MiddlewareMetadata 91 | */ 92 | public get global(): boolean { 93 | return this._global; 94 | } 95 | 96 | /** 97 | * Priority of a global middleware. 98 | * 99 | * @readonly 100 | * @type {number} 101 | * @memberof MiddlewareMetadata 102 | */ 103 | public get priority(): number { 104 | return this._priority; 105 | } 106 | 107 | /** 108 | * Middleware runtime. 109 | * 110 | * @readonly 111 | * @type {(MiddlewareRuntime | undefined)} 112 | * @memberof MiddlewareMetadata 113 | */ 114 | public get runtime(): MiddlewareRuntime { 115 | return this._runtime; 116 | } 117 | 118 | /** 119 | * Entities on which to apply the middleware. 120 | * 121 | * @type {string[]} 122 | * @memberof MiddlewareMetadata 123 | */ 124 | public get entities(): string[] | undefined { 125 | return this._entities; 126 | } 127 | 128 | /** 129 | * Middleware parameters. 130 | * 131 | * @memberof MiddlewareMetadata 132 | */ 133 | public set params(value: ParamMetadata[]) { 134 | this._params = value; 135 | } 136 | 137 | /** 138 | * Entities on which to apply the middleware. 139 | * 140 | * @memberof MiddlewareMetadata 141 | */ 142 | public set entities(value: string[] | undefined) { 143 | this._entities = value; 144 | } 145 | 146 | /** 147 | * User checker. 148 | * 149 | * @memberof MiddlewareMetadata 150 | */ 151 | public set userChecker(value: UserCheckerMetadata | undefined) { 152 | this._userChecker = value; 153 | } 154 | 155 | /** 156 | * Returns the instance of the middleware target. 157 | * 158 | * @readonly 159 | * @type {ICdsMiddleware} 160 | * @memberof MiddlewareMetadata 161 | */ 162 | get instance(): ICdsMiddleware { 163 | return getFromContainer(this._target); 164 | } 165 | 166 | /** 167 | * Default constructor. 168 | * 169 | * @param {IMiddlewareMetadataArgs} args Middleware metadata arguments 170 | * @memberof MiddlewareMetadata 171 | */ 172 | constructor(args: IMiddlewareMetadataArgs) { 173 | super(); 174 | 175 | this._global = args.global; 176 | this._target = args.target; 177 | this._priority = args.priority; 178 | this._runtime = args.runtime; 179 | } 180 | 181 | /** 182 | * Executes the middleware. 183 | * 184 | * @param {IExecContext} context Execution context 185 | * @returns {*} Execution result 186 | * @memberof MiddlewareMetadata 187 | */ 188 | public exec(context: IExecContext): any { 189 | const instance = this.instance; 190 | const params = this.buildParams(this._params, context, this._userChecker); 191 | 192 | if (instance["use"]) { 193 | return instance.use.apply(instance, params); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/metadata/ActionMetadata.ts: -------------------------------------------------------------------------------- 1 | import { IActionMetadataArgs } from "./args/IActionMetadataArgs"; 2 | import { HandlerMetadata } from "./HandlerMetadata"; 3 | import { RejectMetadata } from "./RejectMetadata"; 4 | import { ParamMetadata } from "./ParamMetadata"; 5 | import { HandlerType } from "../types/HandlerType"; 6 | import { ODataOperation } from "../types/ODataOperation"; 7 | import { Executor } from "./base/Executer"; 8 | import { IExecContext } from "../types/IExecContext"; 9 | import { UserCheckerMetadata } from "./UserCheckerMetadata"; 10 | 11 | /** 12 | * Action metadata and executer. 13 | * 14 | * @export 15 | * @class ActionMetadata 16 | */ 17 | export class ActionMetadata extends Executor { 18 | /** 19 | * Parent handler metadata. 20 | * 21 | * @type {HandlerMetadata} 22 | * @memberof ActionMetadata 23 | */ 24 | private _handlerMetadata: HandlerMetadata; 25 | 26 | /** 27 | * Target: Method on the handler class. 28 | * 29 | * @type {Function} 30 | * @memberof ActionMetadata 31 | */ 32 | private _target: Function; 33 | 34 | /** 35 | * Action method on the handler class. 36 | * 37 | * @type {string} 38 | * @memberof ActionMetadata 39 | */ 40 | private _method: string; 41 | 42 | /** 43 | * Entity on which the action acts. 44 | * 45 | * @type {string} 46 | * @memberof ActionMetadata 47 | */ 48 | private _entity?: string; 49 | 50 | /** 51 | * Handler type. 52 | * 53 | * @type {HandlerType} 54 | * @memberof ActionMetadata 55 | */ 56 | private _handler: HandlerType; 57 | 58 | /** 59 | * Operation type. 60 | * 61 | * @type {ODataOperation} 62 | * @memberof ActionMetadata 63 | */ 64 | private _operation: ODataOperation; 65 | 66 | /** 67 | * Function import name. 68 | * 69 | * @type {string} 70 | * @memberof ActionMetadata 71 | */ 72 | private _functionImportName?: string; 73 | 74 | /** 75 | * Reject metadata. 76 | * 77 | * @type {RejectMetadata} 78 | * @memberof ActionMetadata 79 | */ 80 | private _reject?: RejectMetadata; 81 | 82 | /** 83 | * Parameter metadata. 84 | * 85 | * @type {ParamMetadata[]} 86 | * @memberof ActionMetadata 87 | */ 88 | private _params: ParamMetadata[] = []; 89 | 90 | /** 91 | * User checker. 92 | * 93 | * @private 94 | * @type {UserCheckerMetadata} 95 | * @memberof ActionMetadata 96 | */ 97 | private _userChecker?: UserCheckerMetadata; 98 | 99 | /** 100 | * Target: Method on the handler class. 101 | * 102 | * @readonly 103 | * @type {Function} 104 | * @memberof ActionMetadata 105 | */ 106 | public get target(): Function { 107 | return this._target; 108 | } 109 | 110 | /** 111 | * Action method on the handler class. 112 | * 113 | * @readonly 114 | * @type {string} 115 | * @memberof ActionMetadata 116 | */ 117 | public get method(): string { 118 | return this._method; 119 | } 120 | 121 | /** 122 | * Handler type. 123 | * 124 | * @readonly 125 | * @type {HandlerType} 126 | * @memberof ActionMetadata 127 | */ 128 | public get handler(): HandlerType { 129 | return this._handler; 130 | } 131 | 132 | /** 133 | * Operation type. 134 | * 135 | * @readonly 136 | * @type {ODataOperation} 137 | * @memberof ActionMetadata 138 | */ 139 | public get operation(): ODataOperation { 140 | return this._operation; 141 | } 142 | 143 | /** 144 | * Entity on which the action acts. 145 | * 146 | * @readonly 147 | * @type {string} 148 | * @memberof ActionMetadata 149 | */ 150 | public get entity(): string | undefined { 151 | return this._entity; 152 | } 153 | 154 | /** 155 | * Function import name. 156 | * 157 | * @readonly 158 | * @type {(string | undefined)} 159 | * @memberof ActionMetadata 160 | */ 161 | public get functionImportName(): string | undefined { 162 | return this._functionImportName; 163 | } 164 | 165 | /** 166 | * Reject metadata. 167 | * 168 | * @memberof ActionMetadata 169 | */ 170 | public set reject(value: RejectMetadata | undefined) { 171 | this._reject = value; 172 | } 173 | 174 | /** 175 | * Parameter metadata. 176 | * 177 | * @memberof ActionMetadata 178 | */ 179 | public set params(value: ParamMetadata[]) { 180 | this._params = value; 181 | } 182 | 183 | /** 184 | * User checker. 185 | * 186 | * @memberof ActionMetadata 187 | */ 188 | public set userChecker(value: UserCheckerMetadata | undefined) { 189 | this._userChecker = value; 190 | } 191 | 192 | /** 193 | * Default constructor. 194 | * 195 | * @param {HandlerMetadata} handlerMetadata Handler metadata 196 | * @param {IActionMetadataArgs} args Action metadata arguments 197 | * @memberof ActionMetadata 198 | */ 199 | constructor(handlerMetadata: HandlerMetadata, args: IActionMetadataArgs) { 200 | super(); 201 | 202 | this._handlerMetadata = handlerMetadata; 203 | this._entity = handlerMetadata.entity; 204 | this._target = args.target; 205 | this._method = args.method; 206 | this._handler = args.handler; 207 | this._operation = args.operation; 208 | this._functionImportName = args.functionImportName; 209 | } 210 | 211 | /** 212 | * Executs the action. 213 | * 214 | * @protected 215 | * @param {IExecContext} context Execution context 216 | * @returns {*} Execution result 217 | * @memberof ActionMetadata 218 | */ 219 | public exec(context: IExecContext): any { 220 | let result = undefined; 221 | 222 | const instance = this._handlerMetadata.instance; 223 | const params = this.buildParams(this._params, context, this._userChecker); 224 | 225 | if (this._reject) { 226 | try { 227 | result = instance[this._method].apply(instance, params); 228 | } catch (error: any) { 229 | if (this._reject.appendErrorMessage) { 230 | context.req.reject(this._reject.code, `${this._reject.message}: ${error.message}`); 231 | } else { 232 | context.req.reject(this._reject.code, this._reject.message); 233 | } 234 | 235 | return; 236 | } 237 | } else { 238 | result = instance[this._method].apply(instance, params); 239 | } 240 | 241 | return result; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /cds-routing-handlers.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "ab3c5961-53aa-41c9-a813-445e56c14d4f", 4 | "name": "cds-routing-handlers", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "hello Function Import", 10 | "protocolProfileBehavior": { 11 | "disableBodyPruning": true 12 | }, 13 | "request": { 14 | "method": "GET", 15 | "header": [ 16 | { 17 | "key": "Accept", 18 | "value": "application/json", 19 | "type": "text", 20 | "disabled": true 21 | }, 22 | { 23 | "key": "Content-Type", 24 | "name": "Content-Type", 25 | "value": "application/json", 26 | "type": "text" 27 | } 28 | ], 29 | "body": { 30 | "mode": "raw", 31 | "raw": "", 32 | "options": { 33 | "raw": { 34 | "language": "json" 35 | } 36 | } 37 | }, 38 | "url": { 39 | "raw": "localhost:3001/odata/hello(name='test')", 40 | "host": ["localhost"], 41 | "port": "3001", 42 | "path": ["odata", "hello(name='test')"] 43 | } 44 | }, 45 | "response": [] 46 | }, 47 | { 48 | "name": "hello Function Import (v2)", 49 | "protocolProfileBehavior": { 50 | "disableBodyPruning": true 51 | }, 52 | "request": { 53 | "method": "GET", 54 | "header": [ 55 | { 56 | "key": "Accept", 57 | "value": "application/json", 58 | "type": "text", 59 | "disabled": true 60 | }, 61 | { 62 | "key": "Content-Type", 63 | "name": "Content-Type", 64 | "value": "application/json", 65 | "type": "text" 66 | } 67 | ], 68 | "body": { 69 | "mode": "raw", 70 | "raw": "", 71 | "options": { 72 | "raw": { 73 | "language": "json" 74 | } 75 | } 76 | }, 77 | "url": { 78 | "raw": "localhost:3001/v2/odata/hello?name=test", 79 | "host": ["localhost"], 80 | "port": "3001", 81 | "path": ["v2", "odata", "hello"], 82 | "query": [ 83 | { 84 | "key": "name", 85 | "value": "test" 86 | } 87 | ] 88 | } 89 | }, 90 | "response": [] 91 | }, 92 | { 93 | "name": "greeter Action Import", 94 | "request": { 95 | "method": "POST", 96 | "header": [ 97 | { 98 | "key": "Content-Type", 99 | "name": "Content-Type", 100 | "value": "application/json", 101 | "type": "text" 102 | } 103 | ], 104 | "body": { 105 | "mode": "raw", 106 | "raw": "{\n\t\"title\": \"test-title\",\n\t\"name\": \"test-name\"\n}", 107 | "options": { 108 | "raw": { 109 | "language": "json" 110 | } 111 | } 112 | }, 113 | "url": { 114 | "raw": "localhost:3001/odata/greeter", 115 | "host": ["localhost"], 116 | "port": "3001", 117 | "path": ["odata", "greeter"] 118 | } 119 | }, 120 | "response": [] 121 | }, 122 | { 123 | "name": "greeter Action Import (v2)", 124 | "request": { 125 | "method": "POST", 126 | "header": [ 127 | { 128 | "key": "Content-Type", 129 | "name": "Content-Type", 130 | "value": "application/json", 131 | "type": "text" 132 | } 133 | ], 134 | "body": { 135 | "mode": "raw", 136 | "raw": "{\n\t\"title\": \"test-title\",\n\t\"name\": \"test-name\"\n}", 137 | "options": { 138 | "raw": { 139 | "language": "json" 140 | } 141 | } 142 | }, 143 | "url": { 144 | "raw": "localhost:3001/v2/odata/greeter?title=noop&name=sepp", 145 | "host": ["localhost"], 146 | "port": "3001", 147 | "path": ["v2", "odata", "greeter"], 148 | "query": [ 149 | { 150 | "key": "title", 151 | "value": "noop" 152 | }, 153 | { 154 | "key": "name", 155 | "value": "sepp" 156 | } 157 | ] 158 | } 159 | }, 160 | "response": [] 161 | }, 162 | { 163 | "name": "READ Greeter Entity", 164 | "request": { 165 | "method": "GET", 166 | "header": [], 167 | "url": { 168 | "raw": "localhost:3001/odata/Greeter", 169 | "host": ["localhost"], 170 | "port": "3001", 171 | "path": ["odata", "Greeter"] 172 | } 173 | }, 174 | "response": [] 175 | }, 176 | { 177 | "name": "READ Greeter Entity (v2)", 178 | "request": { 179 | "method": "GET", 180 | "header": [], 181 | "url": { 182 | "raw": "localhost:3001/v2/odata/Greeter", 183 | "host": ["localhost"], 184 | "port": "3001", 185 | "path": ["v2", "odata", "Greeter"] 186 | } 187 | }, 188 | "response": [] 189 | } 190 | ], 191 | "protocolProfileBehavior": {} 192 | } 193 | -------------------------------------------------------------------------------- /src/metadata-builder/MetadataBuilder.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from "../index"; 2 | import { ActionMetadata } from "../metadata/ActionMetadata"; 3 | import { RejectMetadata } from "../metadata/RejectMetadata"; 4 | import { ParamMetadata } from "../metadata/ParamMetadata"; 5 | import { HandlerMetadata } from "../metadata/HandlerMetadata"; 6 | import { MiddlewareMetadata } from "../metadata/MiddlewareMetadata"; 7 | import { IUserCheckerMetadataArgs } from "../metadata/args/IUserCheckerMetadataArgs"; 8 | import { UserCheckerMetadata } from "../metadata/UserCheckerMetadata"; 9 | 10 | /** 11 | * Metadata builder. 12 | * 13 | * @export 14 | * @class MetadataBuilder 15 | */ 16 | export class MetadataBuilder { 17 | /** 18 | * Builds handler metadata for a given set of handler classes. 19 | * 20 | * @param {Function[]} [classes] Handler classes 21 | * @returns {MiddlewareMetadata[]} Build handler metadata 22 | * @memberof MetadataBuilder 23 | */ 24 | public buildHandlerMetadata(classes?: Function[], userChecker?: Function): HandlerMetadata[] { 25 | const userCheckerMetadata = getMetadataArgsStorage().findUserCheckerWithTarget(userChecker); 26 | 27 | return this.createHandler(classes, userCheckerMetadata); 28 | } 29 | 30 | /** 31 | * Builds middleware metadata for a given set of middleware classes. 32 | * 33 | * @param {Function[]} [classes] Middleware classes 34 | * @returns {MiddlewareMetadata[]} Buiild middleware metadata 35 | * @memberof MetadataBuilder 36 | */ 37 | public buildMiddlewareMetadata(classes?: Function[], userChecker?: Function): MiddlewareMetadata[] { 38 | const userCheckerMetadata = getMetadataArgsStorage().findUserCheckerWithTarget(userChecker); 39 | 40 | return this.createMiddlewares(classes, userCheckerMetadata); 41 | } 42 | 43 | /** 44 | * Create handler metadata. 45 | * 46 | * @param {Function[]} [classes] Handler classes to build for 47 | * @returns {MiddlewareMetadata[]} Created handler metadata 48 | * @memberof MetadataBuilder 49 | */ 50 | private createHandler(classes?: Function[], userCheckerMetadataArg?: IUserCheckerMetadataArgs): HandlerMetadata[] { 51 | const handlers = !classes 52 | ? getMetadataArgsStorage().handlerMetadata 53 | : getMetadataArgsStorage().filterHandlerMetadataForClasses(classes); 54 | 55 | return handlers.map(handlerArgs => { 56 | const handler = new HandlerMetadata(handlerArgs); 57 | handler.actions = this.createActions(handler, userCheckerMetadataArg); 58 | 59 | return handler; 60 | }); 61 | } 62 | 63 | /** 64 | * Creates middleware metadata. 65 | * 66 | * @private 67 | * @param {Function[]} [classes] Middleware classes to build for 68 | * @returns {MiddlewareMetadata[]} Created middleware metadata 69 | * @memberof MetadataBuilder 70 | */ 71 | private createMiddlewares( 72 | classes?: Function[], 73 | userCheckerMetadataArg?: IUserCheckerMetadataArgs 74 | ): MiddlewareMetadata[] { 75 | const middlewares = !classes 76 | ? getMetadataArgsStorage().middlewareMetadata 77 | : getMetadataArgsStorage().filterMiddlewareMetadataForClasses(classes); 78 | 79 | return middlewares.map(middlewareArgs => { 80 | const middleware = new MiddlewareMetadata(middlewareArgs); 81 | middleware.params = this.createMiddlewareParams(middleware); 82 | middleware.userChecker = this.createUserChecker(userCheckerMetadataArg); 83 | 84 | if (!middleware.global) { 85 | const uses = getMetadataArgsStorage().filterUsesWithMiddleware(middleware.target); 86 | const handler = getMetadataArgsStorage().filterHandlerMetadataForClasses(uses.map(u => u.target)); 87 | middleware.entities = handler 88 | .filter(h => h.entity) 89 | .map(h => h.entity || "") 90 | .filter(s => s !== ""); 91 | } 92 | 93 | return middleware; 94 | }); 95 | } 96 | 97 | /** 98 | * Creates action metadata. 99 | * 100 | * @param {MiddlewareMetadata} handler Handler metadata 101 | * @returns {ActionMetadata[]} Created action metadata 102 | * @memberof MetadataBuilder 103 | */ 104 | private createActions(handler: HandlerMetadata, userCheckerArg?: IUserCheckerMetadataArgs): ActionMetadata[] { 105 | return getMetadataArgsStorage() 106 | .filterActionsWithTarget(handler.target) 107 | .map(actionArgs => { 108 | const action = new ActionMetadata(handler, actionArgs); 109 | 110 | action.reject = this.createReject(action); 111 | action.params = this.createParams(action); 112 | action.userChecker = this.createUserChecker(userCheckerArg); 113 | 114 | return action; 115 | }); 116 | } 117 | 118 | /** 119 | * Creates a reject. 120 | * 121 | * @private 122 | * @param {ActionMetadata} action Action to create metadata for 123 | * @returns {(RejectMetadata | undefined)} Created rejection metadata 124 | * @memberof MetadataBuilder 125 | */ 126 | private createReject(action: ActionMetadata): RejectMetadata | undefined { 127 | let result; 128 | 129 | const args = getMetadataArgsStorage().filterRejectWithTargetAndMethod(action.target, action.method); 130 | if (args) { 131 | result = new RejectMetadata(args); 132 | } 133 | 134 | return result; 135 | } 136 | 137 | /** 138 | * Creates action parameters. 139 | * 140 | * @private 141 | * @param {ActionMetadata} action Action to create the parameters for 142 | * @returns {ParamMetadata[]} Created parameters 143 | * @memberof MetadataBuilder 144 | */ 145 | private createParams(action: ActionMetadata): ParamMetadata[] { 146 | return getMetadataArgsStorage() 147 | .filterParamsWithTargetAndMethod(action.target, action.method) 148 | .map(paramArgs => new ParamMetadata(paramArgs)); 149 | } 150 | 151 | /** 152 | * Creates middleware paramters. 153 | * 154 | * @private 155 | * @param {MiddlewareMetadata} middleware Middleware to create parameters for 156 | * @returns {ParamMetadata[]} Created parameters 157 | * @memberof MetadataBuilder 158 | */ 159 | private createMiddlewareParams(middleware: MiddlewareMetadata): ParamMetadata[] { 160 | return getMetadataArgsStorage() 161 | .filterParamsWithTargetAndMethod(middleware.target, "use") 162 | .map(paramArgs => new ParamMetadata(paramArgs)); 163 | } 164 | 165 | /** 166 | * Creates user checker parameters. 167 | * 168 | * @private 169 | * @param {UserCheckerMetadata} userchecker Middleware to create parameters for 170 | * @returns {ParamMetadata[]} Created parameters 171 | * @memberof MetadataBuilder 172 | */ 173 | private createUserCheckerParams(userChecker: UserCheckerMetadata): ParamMetadata[] { 174 | return getMetadataArgsStorage() 175 | .filterParamsWithTargetAndMethod(userChecker.target, "check") 176 | .map(paramArgs => new ParamMetadata(paramArgs)); 177 | } 178 | 179 | /** 180 | * Creates a user checker. 181 | * 182 | * @private 183 | * @param {(IUserCheckerMetadataArgs | undefined)} userCheckerArg User checker metadata arguments 184 | * @returns {(UserCheckerMetadata | undefined)} Creates user checker 185 | * @memberof MetadataBuilder 186 | */ 187 | private createUserChecker(userCheckerArg: IUserCheckerMetadataArgs | undefined): UserCheckerMetadata | undefined { 188 | if (userCheckerArg) { 189 | const userChecker = new UserCheckerMetadata(userCheckerArg); 190 | userChecker.params = this.createUserCheckerParams(userChecker); 191 | 192 | return userChecker; 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/metadata-builder/MetadataArgsStorage.ts: -------------------------------------------------------------------------------- 1 | import { IHandlerMetadataArgs } from "../metadata/args/IHandlerMetadataArgs"; 2 | import { IActionMetadataArgs } from "../metadata/args/IActionMetadataArgs"; 3 | import { IRejectMetadataArgs } from "../metadata/args/IRejectMetadataArgs"; 4 | import { IParamMetadataArgs } from "../metadata/args/IParamMetadataArgs"; 5 | import { IUseMetadataArgs } from "../metadata/args/IUseMetadataArgs"; 6 | import { IMiddlewareMetadataArgs } from "../metadata/args/IMiddlewareMetadataArgs"; 7 | import { IUserCheckerMetadataArgs } from "../metadata/args/IUserCheckerMetadataArgs"; 8 | 9 | /** 10 | * Metadata arguments storage. 11 | * 12 | * @export 13 | * @class MetadataArgsStorage 14 | */ 15 | export class MetadataArgsStorage { 16 | /** 17 | * Handler metadata arguments. 18 | * 19 | * @private 20 | * @type {IHandlerMetadataArgs[]} 21 | * @memberof MetadataArgsStorage 22 | */ 23 | private handler: IHandlerMetadataArgs[] = []; 24 | 25 | /** 26 | * Middleware metadata arguments. 27 | * 28 | * @private 29 | * @type {IMiddlewareMetadataArgs[]} 30 | * @memberof MetadataArgsStorage 31 | */ 32 | private middlewares: IMiddlewareMetadataArgs[] = []; 33 | 34 | /** 35 | * Registerd middleware usage metadata args. 36 | * 37 | * @private 38 | * @type {IUseMetadataArgs[]} 39 | * @memberof MetadataArgsStorage 40 | */ 41 | private uses: IUseMetadataArgs[] = []; 42 | 43 | /** 44 | * Action metadata arguments. 45 | * 46 | * @private 47 | * @type {IActionMetadataArgs[]} 48 | * @memberof MetadataArgsStorage 49 | */ 50 | private actions: IActionMetadataArgs[] = []; 51 | 52 | /** 53 | * Reject metadata arguments. 54 | * 55 | * @private 56 | * @type {IRejectMetadataArgs[]} 57 | * @memberof MetadataArgsStorage 58 | */ 59 | private rejects: IRejectMetadataArgs[] = []; 60 | 61 | /** 62 | * Parameter metadata arguments. 63 | * 64 | * @private 65 | * @type {IParamMetadataArgs[]} 66 | * @memberof MetadataArgsStorage 67 | */ 68 | private params: IParamMetadataArgs[] = []; 69 | 70 | /** 71 | * User checker metadata arguments. 72 | * 73 | * @private 74 | * @type {IUserCheckerMetadataArgs} 75 | * @memberof MetadataArgsStorage 76 | */ 77 | private userChecker: IUserCheckerMetadataArgs[] = []; 78 | 79 | /** 80 | * Handler metadata. 81 | * 82 | * @readonly 83 | * @type {IHandlerMetadataArgs[]} 84 | * @memberof MetadataArgsStorage 85 | */ 86 | public get handlerMetadata(): IHandlerMetadataArgs[] { 87 | return this.handler; 88 | } 89 | 90 | /** 91 | * Middleware metadata. 92 | * 93 | * @readonly 94 | * @type {IMiddlewareMetadataArgs[]} 95 | * @memberof MetadataArgsStorage 96 | */ 97 | public get middlewareMetadata(): IMiddlewareMetadataArgs[] { 98 | return this.middlewares; 99 | } 100 | 101 | /** 102 | * Adds handler metadata. 103 | * 104 | * @param {IHandlerMetadataArgs} metadata Metadata arguments 105 | * @memberof MetadataArgsStorage 106 | */ 107 | public addHandlerMetadata(metadata: IHandlerMetadataArgs): void { 108 | this.handler.push(metadata); 109 | } 110 | 111 | /** 112 | * Adds middleware metadata. 113 | * 114 | * @param {IMiddlewareMetadataArgs} metadata Metadata arguments 115 | * @memberof MetadataArgsStorage 116 | */ 117 | public addMiddlewareMetadata(metadata: IMiddlewareMetadataArgs): void { 118 | this.middlewares.push(metadata); 119 | } 120 | 121 | /** 122 | * Adds a middleware usage metadata. 123 | * 124 | * @param {IUseMetadataArgs} metadata Metadata arguments 125 | * @memberof MetadataArgsStorage 126 | */ 127 | public addUseMetadata(metadata: IUseMetadataArgs): void { 128 | this.uses.push(metadata); 129 | } 130 | 131 | /** 132 | * Adds action metadata. 133 | * 134 | * @param {IActionMetadataArgs} metadata Metadata arguments 135 | * @memberof MetadataArgsStorage 136 | */ 137 | public addActionMetadata(metadata: IActionMetadataArgs): void { 138 | this.actions.push(metadata); 139 | } 140 | 141 | /** 142 | * Adds reject metadata. 143 | * 144 | * @param {IRejectMetadataArgs} metadata Metadata arguements 145 | * @memberof MetadataArgsStorage 146 | */ 147 | public addRejectMetadata(metadata: IRejectMetadataArgs): void { 148 | if (this.filterRejectWithTargetAndMethod(metadata.target, metadata.method) === undefined) { 149 | this.rejects.push(metadata); 150 | } else { 151 | console.warn(`Only one OnReject can be registerd per action (${metadata.target.name}::${metadata.method})`); 152 | } 153 | } 154 | 155 | /** 156 | * Adds parameter metadata. 157 | * 158 | * @param {IParamMetadataArgs} metadata Metadata arguments 159 | * @memberof MetadataArgsStorage 160 | */ 161 | public addParamMetadata(metadata: IParamMetadataArgs): void { 162 | this.params.push(metadata); 163 | } 164 | 165 | /** 166 | * Adds user checker metadata. 167 | * 168 | * @param {IUserCheckerMetadataArgs} metadata Metadata arguments 169 | * @memberof MetadataArgsStorage 170 | */ 171 | public addUserCheckerMetadata(metadata: IUserCheckerMetadataArgs): void { 172 | this.userChecker.push(metadata); 173 | } 174 | 175 | /** 176 | * Filters handler metadata for given classes. 177 | * 178 | * @param {Function[]} classes Handler classes 179 | * @returns {IHandlerMetadataArgs[]} Filtered handler metadata 180 | * @memberof MetadataArgsStorage 181 | */ 182 | public filterHandlerMetadataForClasses(classes: Function[]): IHandlerMetadataArgs[] { 183 | return this.handler.filter(ctrl => { 184 | return classes.filter(cls => ctrl.target === cls).length > 0; 185 | }); 186 | } 187 | 188 | /** 189 | * Filters middleware metadata for given classes. 190 | * 191 | * @param {Function} classes Middleware classes 192 | * @returns {IMiddlewareMetadataArgs[]} Filtered middleware metadata 193 | * @memberof MetadataArgsStorage 194 | */ 195 | public filterMiddlewareMetadataForClasses(classes: Function[]): IMiddlewareMetadataArgs[] { 196 | const middlewares = classes.map(cls => this.middlewares.find(mid => mid.target === cls)); 197 | return middlewares.filter(midd => midd !== undefined) as IMiddlewareMetadataArgs[]; 198 | } 199 | 200 | /** 201 | * Filters registerd middleware usages for a given middleware. 202 | * 203 | * @param {Function} middleware Middleware to filter for 204 | * @returns {IUseMetadataArgs[]} Filterd usage metadata 205 | * @memberof MetadataArgsStorage 206 | */ 207 | public filterUsesWithMiddleware(middleware: Function): IUseMetadataArgs[] { 208 | return this.uses.filter(use => { 209 | return use.middleware === middleware; 210 | }); 211 | } 212 | 213 | /** 214 | * Filters action metadata for given target. 215 | * 216 | * @param {Function} target Target of the action 217 | * @returns {IActionMetadataArgs[]} Filtered action metadata 218 | * @memberof MetadataArgsStorage 219 | */ 220 | public filterActionsWithTarget(target: Function): IActionMetadataArgs[] { 221 | return this.actions.filter(action => action.target === target); 222 | } 223 | 224 | /** 225 | * Filters reject metadata for given target and method. 226 | * 227 | * @param {Function} target Target of the reject 228 | * @param {string} method Method of the reject 229 | * @returns {IRejectMetadataArgs} 230 | * @memberof MetadataArgsStorage 231 | */ 232 | public filterRejectWithTargetAndMethod(target: Function, method: string): IRejectMetadataArgs | undefined { 233 | let result; 234 | 235 | const rejects = this.rejects.filter(reject => reject.target === target && reject.method === method); 236 | if (rejects.length === 1) { 237 | result = rejects[0]; 238 | } 239 | 240 | return result; 241 | } 242 | 243 | /** 244 | * Filters parameter metadata for a given target and method. 245 | * 246 | * @param {Function} target Target of the parameter 247 | * @param {string} method Method of the parameter 248 | * @returns {IParamMetadataArgs[]} 249 | * @memberof MetadataArgsStorage 250 | */ 251 | public filterParamsWithTargetAndMethod(target: Function, method: string): IParamMetadataArgs[] { 252 | return this.params.filter(param => param.object.constructor === target && param.method === method); 253 | } 254 | 255 | /** 256 | * Finds a user checker for a given target. 257 | * 258 | * @param {Function} target Target to find for 259 | * @returns {(IUserCheckerMetadataArgs | undefined)} Found user checker 260 | * @memberof MetadataArgsStorage 261 | */ 262 | public findUserCheckerWithTarget(target: Function | undefined): IUserCheckerMetadataArgs | undefined { 263 | if (target) { 264 | return this.userChecker.find(uc => uc.target === target); 265 | } 266 | } 267 | 268 | /** 269 | * Resets the storage. 270 | * 271 | * @memberof MetadataArgsStorage 272 | */ 273 | public reset(): void { 274 | this.handler = []; 275 | this.middlewares = []; 276 | this.uses = []; 277 | this.actions = []; 278 | this.rejects = []; 279 | this.params = []; 280 | this.userChecker = []; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/CDSHandler.ts: -------------------------------------------------------------------------------- 1 | import { MetadataBuilder } from "./metadata-builder/MetadataBuilder"; 2 | import { ActionMetadata } from "./metadata/ActionMetadata"; 3 | import { HandlerType } from "./types/HandlerType"; 4 | import { ODataOperation } from "./types/ODataOperation"; 5 | import { IExecContext } from "./types/IExecContext"; 6 | import { MiddlewareMetadata } from "./metadata/MiddlewareMetadata"; 7 | import { IRegisterOptions } from "./types/IRegisterOptions"; 8 | import { MiddlewareRuntime } from "./types/MiddlewareRuntime"; 9 | 10 | /** 11 | * CDS Handler. 12 | * 13 | * @export 14 | * @class CDSHandler 15 | */ 16 | export class CDSHandler { 17 | /** 18 | * Registers all handlers and middlewares on the given service. 19 | * 20 | * @static 21 | * @param {*} srv Service to register the handlers on 22 | * @param {Function[]} handlerClasses Handler classes 23 | * @param {Function[]} middlewareClasses Middleware classes 24 | * @memberof CDSHandler 25 | */ 26 | public static register(srv: any, options: IRegisterOptions): void { 27 | const metadataBuilder = new MetadataBuilder(); 28 | 29 | if (options.middlewares) { 30 | this.registerMiddlewareClasses(metadataBuilder, srv, options.middlewares, options.userChecker); 31 | } 32 | 33 | this.registerHandlerClasses(metadataBuilder, srv, options.handler, options.userChecker); 34 | } 35 | 36 | /** 37 | * Registers all middleware classes. 38 | * 39 | * @private 40 | * @static 41 | * @param {MetadataBuilder} metadataBuilder Metadata builder 42 | * @param {*} srv Service to register the handlers to 43 | * @param {Function[]} middlewareClasses Middleware classes 44 | * @memberof CDSHandler 45 | */ 46 | private static registerMiddlewareClasses( 47 | metadataBuilder: MetadataBuilder, 48 | srv: any, 49 | middlewareClasses: Function[], 50 | userChecker?: Function 51 | ): void { 52 | const middlewares = metadataBuilder.buildMiddlewareMetadata(middlewareClasses, userChecker); 53 | const globalSortedMiddlewares = middlewares 54 | .filter((m) => m.global) 55 | .sort((a, b) => { 56 | if (a.priority > b.priority) return 1; 57 | if (b.priority > a.priority) return -1; 58 | 59 | return 0; 60 | }); 61 | 62 | globalSortedMiddlewares.forEach((middleware) => { 63 | this.registerMiddleware(srv, middleware); 64 | }); 65 | 66 | const usageMiddlewares = middlewares.filter((m) => !m.global); 67 | usageMiddlewares.forEach((middleware) => { 68 | this.registerMiddleware(srv, middleware); 69 | }); 70 | } 71 | 72 | /** 73 | * Register all handler classes. 74 | * 75 | * @private 76 | * @static 77 | * @param {MetadataBuilder} metadataBuilder Metadata builder 78 | * @param {*} srv Service to register the handlers to 79 | * @param {Function[]} handlerClasses Handler classes 80 | * @memberof CDSHandler 81 | */ 82 | private static registerHandlerClasses( 83 | metadataBuilder: MetadataBuilder, 84 | srv: any, 85 | handlerClasses: Function[], 86 | userChecker?: Function 87 | ): void { 88 | const handlers = metadataBuilder.buildHandlerMetadata(handlerClasses, userChecker); 89 | handlers.forEach((handler) => { 90 | handler.actions.forEach((action) => { 91 | try { 92 | switch (action.handler) { 93 | case HandlerType.Before: 94 | this.registerBeforeHandler(srv, action); 95 | break; 96 | 97 | case HandlerType.On: 98 | if ( 99 | action.operation === ODataOperation.Function || 100 | action.operation === ODataOperation.Action 101 | ) { 102 | this.registerFunctionImportHandler(srv, action); 103 | } else { 104 | this.registerOnHandler(srv, action); 105 | } 106 | break; 107 | 108 | case HandlerType.After: 109 | this.registerAfterHandler(srv, action); 110 | } 111 | } catch (error: any) { 112 | console.error(error.message); 113 | } 114 | }); 115 | }); 116 | } 117 | 118 | /** 119 | * Registers before handler. 120 | * 121 | * @private 122 | * @static 123 | * @param {*} srv Service to register to 124 | * @param {ActionMetadata} action Action to register 125 | * @memberof CDSHandler 126 | */ 127 | private static registerBeforeHandler(srv: any, action: ActionMetadata): void { 128 | if (action.entity !== undefined) { 129 | srv.before(action.operation, action.entity, async (req: any, next: Function) => { 130 | const context = this.createExecutionContext(srv, req, next); 131 | return await action.exec(context); 132 | }); 133 | } 134 | } 135 | 136 | /** 137 | * Registers on handler. 138 | * 139 | * @private 140 | * @static 141 | * @param {*} srv Service to register to 142 | * @param {ActionMetadata} action Action to register 143 | * @memberof CDSHandler 144 | */ 145 | private static registerOnHandler(srv: any, action: ActionMetadata): void { 146 | if (action.entity !== undefined) { 147 | srv.on(action.operation, action.entity, async (req: any, next: Function) => { 148 | const context = this.createExecutionContext(srv, req, next); 149 | return await action.exec(context); 150 | }); 151 | } 152 | } 153 | 154 | /** 155 | * Registers after handler. 156 | * 157 | * @private 158 | * @static 159 | * @param {*} srv Service to register to 160 | * @param {ActionMetadata} action Action to register 161 | * @memberof CDSHandler 162 | */ 163 | private static registerAfterHandler(srv: any, action: ActionMetadata): void { 164 | if (action.entity !== undefined) { 165 | srv.after(action.operation, action.entity, async (entities: any[], req: any) => { 166 | const context = this.createExecutionContext(srv, req, undefined, entities); 167 | return await action.exec(context); 168 | }); 169 | } 170 | } 171 | 172 | /** 173 | * Registers function import handler. 174 | * 175 | * @private 176 | * @static 177 | * @param {*} srv Service to register to 178 | * @param {ActionMetadata} action Action to register 179 | * @memberof CDSHandler 180 | */ 181 | private static registerFunctionImportHandler(srv: any, action: ActionMetadata): void { 182 | if (action.entity === undefined) { 183 | srv.on(action.functionImportName, async (req: any, next: Function) => { 184 | const context = this.createExecutionContext(srv, req, next); 185 | return await action.exec(context); 186 | }); 187 | } else { 188 | srv.on(action.functionImportName, action.entity, async (req: any, next: Function) => { 189 | const context = this.createExecutionContext(srv, req, next); 190 | return await action.exec(context); 191 | }); 192 | } 193 | } 194 | 195 | /** 196 | * Registers a middleware with CDS. 197 | * 198 | * @private 199 | * @static 200 | * @param {*} srv Service to register to 201 | * @param {MiddlewareMetadata} middleware Middleware to register 202 | * @memberof CDSHandler 203 | */ 204 | private static registerMiddleware(srv: any, middleware: MiddlewareMetadata): void { 205 | switch (middleware.runtime as MiddlewareRuntime) { 206 | case MiddlewareRuntime.Normal: 207 | srv.before("*", async (req: any, next: Function) => { 208 | const context = this.createExecutionContext(srv, req, next); 209 | return await middleware.exec(context); 210 | }); 211 | 212 | break; 213 | case MiddlewareRuntime.BeforeDefaults: 214 | if (srv?._handlers?.initial?._handlers) { 215 | srv._handlers.initial._handlers.unshift({ 216 | event: "*", 217 | entity: undefined, 218 | handler: async (req: any, next: Function) => { 219 | const context = this.createExecutionContext(srv, req, next); 220 | return await middleware.exec(context); 221 | }, 222 | }); 223 | } else { 224 | console.log("WARNING cds-routing-handler: Can't register MiddlewareRuntime.BeforeDefaults Handler"); 225 | } 226 | break; 227 | case MiddlewareRuntime.AfterDefaults: 228 | if (srv?._initial) { 229 | srv._initial("*", async (req: any, next: Function) => { 230 | const context = this.createExecutionContext(srv, req, next); 231 | return await middleware.exec(context); 232 | }); 233 | } else { 234 | console.log("WARNING cds-routing-handler: Can't register MiddlewareRuntime.AfterDefaults Handler"); 235 | } 236 | break; 237 | } 238 | } 239 | 240 | /** 241 | * Creates a execution context. 242 | * 243 | * @private 244 | * @static 245 | * @param {*} srv CDS service 246 | * @param {*} req Incoming CDS request 247 | * @param {(Function | undefined)} next Next handler function 248 | * @param {(any[] | any)} [e] Entities on a after handler 249 | * @returns {IExecContext} 250 | * @memberof CDSHandler 251 | */ 252 | private static createExecutionContext( 253 | srv: any, 254 | req: any, 255 | next: Function | undefined, 256 | e?: any[] | any 257 | ): IExecContext { 258 | return { 259 | srv: srv, 260 | req: req, 261 | next: next, 262 | e: e, 263 | }; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDS Routing-Handlers 2 | 3 | [![npm version](https://badge.fury.io/js/cds-routing-handlers.svg)](https://badge.fury.io/js/cds-routing-handlers) [![Actions Status](https://github.com/mrbandler/cds-routing-handlers/workflows/build/badge.svg)](https://github.com/mrbandler/cds-routing-handlers/actions) [![GitHub License](https://img.shields.io/github/license/mrbandler/cds-routing-handlers)](https://github.com/mrbandler/cds-routing-handlers/blob/master/LICENSE) 4 | 5 | **Package to route and implement CDS handlers via a class based system in Typescript.** 6 | 7 | ## Table of Content 8 | 9 | 1. [Installation](#1-installation) 💻 10 | 2. [Usage](#2-usage) ⌨️ 11 | 3. [Decorator Reference](#3-decorator-reference) 📋 12 | 4. [Example](#4-example) 🧷 13 | 5. [Bugs and Features](#5-bugs-and-features) 🐞💡 14 | 6. [License](#6-license) 📃 15 | 16 | ## 1. Installation 17 | 18 | ```bash 19 | $ npm install cds-routing-handlers 20 | 21 | OR 22 | 23 | $ yarn add cds-routing-handlers 24 | ``` 25 | 26 | Before you can use it you also need to install `reflect-metadata`. 27 | 28 | ```bash 29 | $ npm install reflect-metadata 30 | 31 | OR 32 | 33 | $ yarn add reflect-metadata 34 | ``` 35 | 36 | Once installed, make sure you import it before using CDS Routing-Handlers. 37 | My recommendation would be to place the import at the top of your main entry file. 38 | 39 | ```typescript 40 | import "reflect-metadata"; 41 | ``` 42 | 43 | Finally, one last step is required to use it. We need to tell the Typescript compiler to use decorators. 44 | 45 | Place the following settings in your `tsconfig.json`: 46 | 47 | ```json 48 | { 49 | "emitDecoratorMetadata": true, 50 | "experimentalDecorators": true 51 | } 52 | ``` 53 | 54 | ## 2. Usage 55 | 56 | **Before:** 57 | 58 | ```javascript 59 | const express = require("express"); 60 | 61 | function registerHandlers(srv) { 62 | srv.on("READ", "Entity", async () => { 63 | // Handle the read here... 64 | }); 65 | } 66 | 67 | const server = express(); 68 | cds.serve("./gen/") 69 | .at("odata") 70 | .in(server) 71 | .with(registerHandlers); 72 | ``` 73 | 74 | **With CDS Routing-Handlers:** 75 | 76 | ```typescript 77 | // ./handlers/entity.handler.ts 78 | 79 | import { Handler, OnRead } from "cds-routing-handlers"; 80 | 81 | @Handler("Entity") 82 | export class EntityHandler { 83 | @OnRead() 84 | public async read(@Srv() srv: any, @Req() req: any): Promise { 85 | // Handle the read here... 86 | } 87 | } 88 | ``` 89 | 90 | ```typescript 91 | // ./server.ts 92 | 93 | import express from "express"; 94 | import { createCombinedHandler } from "cds-routing-handlers"; 95 | 96 | const server = express(); 97 | 98 | // Either: 99 | const handler = createCombinedHandler({ 100 | handler: [__dirname + "/handlers/**/*.js"], 101 | }); 102 | 103 | cds.serve("./gen/") 104 | .at("odata") 105 | .in(server) 106 | .with(handler); 107 | 108 | // OR 109 | 110 | import { EntityHandler } from "./handlers/entity.handler.ts"; 111 | 112 | const handler = createCombinedHandler({ 113 | handler: [EntityHandler], 114 | }); 115 | 116 | cds.serve("./gen/") 117 | .at("odata") 118 | .in(server) 119 | .with(handler); 120 | ``` 121 | 122 | ### Depencency Injection Support 123 | 124 | CDS Routing-Handlers can be used in conjunction with [typedi](https://github.com/typestack/typedi). 125 | 126 | ```typescript 127 | import { useContainer } from "cds-routing-handlers"; 128 | import { Container } from "typedi"; 129 | 130 | useContainer(Container); 131 | ``` 132 | 133 | That's it now you can inject service into your handler classes. 134 | 135 | ```typescript 136 | import { Handler, Func, Param } from "cds-routing-handlers"; 137 | import { Service, Inject } from "typedi"; 138 | 139 | @Service() 140 | export class GreeterService { 141 | public greet(name: string): string { 142 | return "Hello, " + name; 143 | } 144 | } 145 | 146 | @Handler() 147 | export class GreeterHandler { 148 | @Inject() 149 | private greeterService: GreeterService; 150 | 151 | @Func("hello") 152 | public async hello(@Param("name") name: string): Promise { 153 | return this.greeterService.greet(name); 154 | } 155 | } 156 | ``` 157 | 158 | ### Middleware 159 | 160 | In addition to the before handler you can implement custom middlewares for either the complete service (global) or a specific entity. 161 | 162 | #### Global Middleware 163 | 164 | ```typescript 165 | // ./global.middleware.ts 166 | 167 | import { ICdsMiddleware, Middleware, Req, Jwt } from "cds-routing-handlers"; 168 | 169 | @Middleware({ global: true, priority: 1 }) 170 | export class GobalMiddleware implements ICdsMiddleware { 171 | // You can inject the request parameters as you would in any other handler. 172 | public async use(@Req() req: any, @Jwt() jwt: string): Promise { 173 | console.log("I am global middleware prio 1"); 174 | } 175 | } 176 | ``` 177 | 178 | ```typescript 179 | // ./server.ts 180 | 181 | import express from "express"; 182 | import { createCombinedHandler } from "cds-routing-handlers"; 183 | import { GlobalMiddleware } from "./global.middleware"; 184 | 185 | const server = express(); 186 | 187 | const handler = createCombinedHandler({ 188 | handler: [__dirname + "/handlers/**/*.js"], 189 | middlewares: [GlobalMiddleware], 190 | }); 191 | 192 | cds.serve("./gen/") 193 | .at("odata") 194 | .in(server) 195 | .with(handler); 196 | ``` 197 | 198 | #### Handler Middleware 199 | 200 | ```typescript 201 | // ./handler.middleware.ts 202 | 203 | import { ICdsMiddleware, Middleware, Req, Jwt } from "cds-routing-handlers"; 204 | 205 | @Middleware() // Omit options for handler middlewares 206 | export class HandlerMiddleware implements ICdsMiddleware { 207 | // You can inject the request parameters as you would in any other handler. 208 | public async use(@Req() req: any, @Jwt() jwt: string): Promise { 209 | console.log("I am handler middleware!"); 210 | } 211 | } 212 | ``` 213 | 214 | ```typescript 215 | // ./handlers/entity.handler.ts 216 | 217 | import { Handler, Use, OnRead } from "cds-routing-handlers"; 218 | import { HandlerMiddleware } from "../handler.middleware"; 219 | 220 | @Handler("Entity") 221 | @Use(HandlerMiddleware) 222 | export class EntityHandler { 223 | @OnRead() 224 | public async read(@Srv() srv: any, @Req() req: any): Promise { 225 | // Handle the read here... 226 | } 227 | } 228 | ``` 229 | 230 | ```typescript 231 | // ./server.ts 232 | 233 | import express from "express"; 234 | import { createCombinedHandler } from "cds-routing-handlers"; 235 | import { HandlerMiddleware } from "./handler.middleware"; 236 | 237 | const server = express(); 238 | 239 | const handler = createCombinedHandler({ 240 | handler: [__dirname + "/handlers/**/*.js"], 241 | middlewares: [HandlerMiddleware], 242 | }); 243 | 244 | cds.serve("./gen/") 245 | .at("odata") 246 | .in(server) 247 | .with(handler); 248 | ``` 249 | 250 | ### User Checker 251 | 252 | To avoid duplicate code for user retrieval in all handlers over the JWT token you can implement a user checker which lets you inject your custom user object with the `@User()` decorator. 253 | 254 | ```typescript 255 | // ./custom.userchecker.ts 256 | 257 | import { IUserChecker, UserChecker, Jwt } from "cds-routing-handlers"; 258 | 259 | export interface IUser { 260 | username: string; 261 | } 262 | 263 | @UserChecker() // Omit options for handler middlewares 264 | export class CustomUserChecker implements IUserChecker { 265 | // You can inject the request parameters as you would in any other handler. 266 | // NOTE: With one exception the @User() decorator will not work. 267 | public async check(@Jwt() jwt: string): Promise { 268 | // Custom code here to create user from JWT... 269 | // Let's fake it here 270 | return { 271 | username: "mrbandler", 272 | }; 273 | } 274 | } 275 | ``` 276 | 277 | ```typescript 278 | // ./handlers/entity.handler.ts 279 | 280 | import { Handler, OnRead } from "cds-routing-handlers"; 281 | import { IUser } from "../custom.userchecker"; 282 | 283 | @Handler("Entity") 284 | export class EntityHandler { 285 | @OnRead() 286 | public async read(@User() user: IUser): Promise { 287 | // Use your custom user object in the read logic... 288 | } 289 | } 290 | ``` 291 | 292 | ```typescript 293 | // ./server.ts 294 | 295 | import express from "express"; 296 | import { createCombinedHandler } from "cds-routing-handlers"; 297 | import { CustomUserChecker } from "./custom.userchecker"; 298 | 299 | const server = express(); 300 | 301 | const handler = createCombinedHandler({ 302 | handler: [__dirname + "/handlers/**/*.js"], 303 | userChecker: CustomUserChecker, 304 | }); 305 | 306 | cds.serve("./gen/") 307 | .at("odata") 308 | .in(server) 309 | .with(handler); 310 | ``` 311 | 312 | ### Complete Handler API 313 | 314 | ```typescript 315 | import { Handler } from "cds-routing-handlers"; 316 | import { BeforeCreate, OnCreate, AfterCreate } from "cds-routing-handlers"; 317 | import { BeforeRead, OnRead, AfterRead } from "cds-routing-handlers"; 318 | import { BeforeUpdate, OnUpdate, AfterUpdate } from "cds-routing-handlers"; 319 | import { BeforeDelete, OnDelete, AfterDelete } from "cds-routing-handlers"; 320 | import { Func, Action } from "cds-routing-handlers"; 321 | import { Req, Srv, Param, ParamObj, Jwt } from "cds-routing-handlers"; 322 | 323 | interface IFoobar { 324 | Id: string; 325 | Foo: string; 326 | Bar: number; 327 | } 328 | 329 | /** 330 | * Basic OData operations. 331 | */ 332 | @Handler("Foobar") 333 | export class FooBarHandler { 334 | /** 335 | * CREATE handlers. 336 | */ 337 | @BeforeCreate() 338 | public async beforeCreate(): Promise { 339 | // Handle the before create here... 340 | } 341 | 342 | @OnCreate() 343 | public async onCreate(): Promise { 344 | // Handle the create here... 345 | } 346 | 347 | @AfterCreate() 348 | public async afterCreate(): Promise { 349 | // Handle the after create here... 350 | } 351 | 352 | /** 353 | * READ handlers. 354 | */ 355 | @BeforeRead() 356 | public async beforeRead(): Promise { 357 | // Handle the before read here... 358 | } 359 | 360 | @OnRead() 361 | public async onRead(): Promise { 362 | // Handle the read here... 363 | } 364 | 365 | @AfterRead() 366 | public async afterRead(): Promise { 367 | // Handle the after read here... 368 | } 369 | 370 | /** 371 | * UPDATE handlers. 372 | */ 373 | @BeforeUpdate() 374 | public async beforeUpdate(): Promise { 375 | // Handle the before update here... 376 | } 377 | 378 | @OnUpdate() 379 | public async onUpdate(): Promise { 380 | // Handle the update here... 381 | } 382 | 383 | @AfterUpdate() 384 | public async afterUpdate(): Promise { 385 | // Handle the after update here... 386 | } 387 | 388 | /** 389 | * DELETE handlers. 390 | */ 391 | @BeforeDelete() 392 | public async beforeDelete(): Promise { 393 | // Handle the before delete here... 394 | } 395 | 396 | @OnDelete() 397 | public async onDelete(): Promise { 398 | // Handle the delete here... 399 | } 400 | 401 | @AfterDelete() 402 | public async afterDelete(): Promise { 403 | // Handle the after delete here... 404 | } 405 | } 406 | 407 | /** 408 | * Root service handler. 409 | */ 410 | @Handler("Foobar") 411 | export class FooBarRejectHandler { 412 | @OnRead() 413 | @OnReject(500, "Foobar not found") 414 | public async onRead(): Promise { 415 | // When something fails in here, a error object like defined above will be returned. 416 | /** 417 | * { 418 | * "error": { 419 | * "code": 500, 420 | * "message": "Foobar not found" 421 | * } 422 | * } 423 | * 424 | */ 425 | } 426 | 427 | @OnRead() 428 | @OnReject(500, "Foobar not found", true) 429 | public async onRead(): Promise { 430 | // When something fails in here, a error object like defined above will be returned. 431 | // This time the JS error message is appended to the message. 432 | /** 433 | * { 434 | * "error": { 435 | * "code": 500, 436 | * "message": "Foobar not found: JS Error Message" 437 | * } 438 | * } 439 | * 440 | */ 441 | } 442 | } 443 | 444 | /** 445 | * Error handling and rejection operations. 446 | */ 447 | @Handler() 448 | export class FooBarRejectHandler { 449 | /** 450 | * Function Import. 451 | * 452 | * CDS: 453 | * function foo(bar: String) returns String; 454 | * 455 | */ 456 | @Func("foo") 457 | public async foo(@Param("bar") bar: string): Promise { 458 | return "Foo, " + bar; 459 | } 460 | 461 | /** 462 | * Action Import. 463 | * 464 | * CDS: 465 | * action bar(foo: String, noop: String); 466 | * 467 | */ 468 | @Action("bar") 469 | public async bar(@Param("foo") foo: string, @Param("noop") noop: string): Promise { 470 | console.log("Foo Param", foo); 471 | console.log("Noop Param", noop); 472 | } 473 | } 474 | 475 | interface IDoItParams { 476 | id: string; 477 | do: string; 478 | times: number; 479 | } 480 | 481 | /** 482 | * Error handling and rejection operations. 483 | */ 484 | @Handler() 485 | export class ParamExampleHandler { 486 | /** 487 | * Function Import. 488 | * 489 | * CDS: 490 | * function hello(title: String, name: String) returns String; 491 | * 492 | */ 493 | @Func("hello") 494 | public async foo(@Param("title") title: string, @Param("name") name: string): Promise { 495 | return `Hello, ${title} ${name}`; 496 | } 497 | 498 | /** 499 | * Function Import. 500 | * 501 | * CDS: 502 | * action doIt(id: String, do: String, tunes: number); 503 | * 504 | */ 505 | @Action("doIt") 506 | public async doIt(@ParamObj() params: IDoItParams): Promise { 507 | console.log(params); // => { id: "12345", do: "over", "times": 9000 } 508 | } 509 | 510 | /** 511 | * Additionaly you can inject the service aswell as the request object. 512 | */ 513 | @OnRead() 514 | public async read(@Srv() srv: any, @Req() req: any): Promise { 515 | // Do something with srv and req. 516 | } 517 | 518 | /** 519 | * If the incoming request contains a JWT token you can inject that aswell. 520 | */ 521 | @OnRead() 522 | public async read(@Jwt() jwt: string): Promise { 523 | // Do something with srv and req. 524 | } 525 | 526 | /** 527 | * After handlers a bit if a special case, they always give you a array of entities that was worked on prior to the after handler. 528 | * 529 | * This list can be injected via the @Entities decorator. 530 | */ 531 | @AfterRead() 532 | public async afterRead(@Entities() entities: IDoItParams[]): Promise { 533 | return entities.map(e => { 534 | e.id = "After handler was here"; 535 | return e; 536 | }); 537 | } 538 | } 539 | ``` 540 | 541 | ## 3. Decorator Reference 542 | 543 | ### Middleware Decorators 544 | 545 | | Signature | Example | Description | 546 | | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 547 | | `@Middleware(options?: { global?: boolean, priority?: number })` | `@Middleware({ global: true, priority: 1 }) class CustomMiddleware implements ICdsMiddleware`
or
`@Middleware() class CustomMiddleware implements ICdsMiddleware` | Class that is marked with this decorator is registered as a middleware and needs to implement the `ICdsMiddleware` interface.
The middleware can then be used to intercept all incoming request via a global definition or intercept all incoming requests on a specific entity handler. | 548 | 549 | ### User Checker Decorators 550 | 551 | | Signature | Example | Description | 552 | | ---------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 553 | | `@UserChecker()` | `@UserChecker() class CustomUserChecker implements IUserChecker` | Class that is marked with this decorator is registered as a user checker and needs to implement the `IUserChecker` interface.
The user checker can then be used to provide a custom user object via the `@User()` decorator. | 554 | 555 | ### Handler Decorators 556 | 557 | | Signature | Example | Description | 558 | | ---------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 559 | | `@Handler(entity?: string)` | `@Handler("Books") class BooksHandler` | Class that is marked with this decorator is registered as a handler and its annotated methods are registered as actions. The entity parameter is used to differentiate and register the correct actions for the corresponding entity. | 560 | | `@Use(...middlewares: Function[])` | `@Use(MiddlewareClass) class BooksHandler` | A class that is marked with this decorator also needs to be marked with the `@Handler()` decorator to be used. If a handler is implemented the `use` method from the middleware is called every time a new requests comes in, regardless of the action (`CREATE`, `READ`, `UPDATE`, `DELETE`) to be executed. | 561 | 562 | ### Handler Action Decorators 563 | 564 | In this table we assume that all action handlers a within a `@Handler` decorated class with the entity `Books`. 565 | 566 | | Signature | Example | Description | @sap/cds analogue | 567 | | ------------------------------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | 568 | | `@BeforeCreate()` | `@BeforeCreate() async beforeCreate()` | Methods marked with this decorator will register a request made with POST HTTP Method to the specified entity, before it will be handled by the actual handler. | `srv.before("CREATE", "Books", async (req) => ...)` | 569 | | `@OnCreate()` | `@OnCreate() async onCreate()` | Methods marked with this decorator will register a request made with POST HTTP Method to the specified entity. | `srv.on("CREATE", "Books", async (req) => ...)` | 570 | | `@AfterCreate()` | `@AfterCreate() async afterCreate()` | Methods marked with this decorator will register a request made with POST HTTP Method to the specified entity, after it was handled by the actual handler. | `srv.after("CREATE", "Books", async (books, req) => ...)` | 571 | | `@BeforeRead()` | `@BeforeRead() async beforeRead()` | Methods marked with this decorator will register a request made with GET HTTP Method to the specified entity, before it will be handled by the actual handler. | `srv.before("READ", "Books", async (req) => ...)` | 572 | | `@OnRead()` | `@OnRead() async onRead()` | Methods marked with this decorator will register a request made with GET HTTP Method to the specified entity. | `srv.on("READ", "Books", async (req) => ...)` | 573 | | `@AfterRead()` | `@AfterRead() async afterRead()` | Methods marked with this decorator will register a request made with GET HTTP Method to the specified entity, after it was handled by the actual handler. | `srv.after("READ", "Books", async (books, req) => ...)` | 574 | | `@BeforeUpdate()` | `@BeforeUpdate() async beforeUpdate()` | Methods marked with this decorator will register a request made with PUT HTTP Method to the specified entity, before it will be handled by the actual handler. | `srv.before("UPDATE", "Books", async (req) => ...)` | 575 | | `@OnUpdate()` | `@OnUpdate() async onUpdate()` | Methods marked with this decorator will register a request made with PUT HTTP Method to the specified entity. | `srv.on("UPDATE", "Books", async (req) => ...)` | 576 | | `@AfterUpdate()` | `@AfterUpdate() async afterUpdate()` | Methods marked with this decorator will register a request made with DELETE HTTP Method to the specified entity, after it was handled by the actual handler. | `srv.after("UPDATE", "Books", async (books, req) => ...)` | 577 | | `@BeforeDelete()` | `@BeforeDelete() async beforeDelete()` | Methods marked with this decorator will register a request made with DELETE HTTP Method to the specified entity, before it will be handled by the actual handler. | `srv.before("DELETE", "Books", async (req) => ...)` | 578 | | `@OnDelete()` | `@OnDelete() async onDelete()` | Methods marked with this decorator will register a request made with DELETE HTTP Method to the specified entity. | `srv.on("DELETE", "Books", async (req) => ...)` | 579 | | `@AfterDelete()` | `@AfterDelete() async afterDelete()` | Methods marked with this decorator will register a request made with DELETE HTTP Method to the specified entity, after it was handled by the actual handler. | `srv.after("DELETE", "Books", async (books, req) => ...)` | 580 | | `@Func(name: string)` | `@Func("doIt") async doIt()` | Methods marked with this decorator will register a request made with either GET or POST HTTP. The payload can either be given by path parameters or body. | `srv.on("doIt", "Books", async (req) => ...)` OR `srv.on("doIt", async (req) => ...)` | 581 | | `@Action(name: string)` | `@Action("doItNow") async doItNow()` | Methods marked with this decorator will register a request made with either GET or POST HTTP. The payload can either be given by path parameters or body. | `srv.on("doItNow", "Books", async (req) => ...)` OR `srv.on("doItNow", async (req) => ...)` | 582 | | `@OnReject(code: number, message: string, appendErrorMessage: boolean = false)` | `@OnReject(500, "Nope, that didn't work") async handler()` | Methods marked with this decorator will return a error object when a handler fails. | `srv.on("READ", "Books", async (req) => {req.reject(500, "Nope, that didn't work")})` | 583 | 584 | ### Method Parameter Decorators 585 | 586 | | Signature | Example | Description | @sap/cds analogue | 587 | | ---------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | 588 | | `@Req()` | `onRead(@Req() req: any)` | Injects the request object. | `srv.on("READ", "Books", async (req) => ...)` | 589 | | `@Srv()` | `onRead(@Srv() srv: any)` | Injects the service object. | `srv.on("READ", "Books", async (req) => { // Acess srv here })` | 590 | | `@Param(name: string)` | `doIt(@Param("times") times: number)` | Injects a Function/Action Import parameter. | `srv.on("READ", "Books", async (req) => { let times = req.data["times"] as number; })` | 591 | | `@ParamObj()` | `doIt(@ParamObj() doItParams: IDoItParams)` | Injects a Function/Action Import parameter object. | `srv.on("READ", "Books", async (req) => { let doItParams = req.data as IDoItParams; })` | 592 | | `@Entities()` | `afterRead(@Entities() entities: IBook[])` | Injects entities from a previous handler on `@After*` handlers. | `srv.after("READ", "Books", async (books, req) => ...)` | 593 | | `@Jwt()` | `onRead(@Jwt() jwt: string)` | Injects the JWT token, when found on incoming request. | `N/A` | 594 | | `@Data()` | `onRead(@Data() book: IBook)` | Injects the data object from a incoming request. It's actually the same as `@ParamObj()` with a different identifier to differentiate between Function/Action imports and other handlers. | `srv.on("READ", "Books", async(req) => { const book = req.data as I Book})` | 595 | | `@Next()` | `onRead(@Next() next: Function)` | Injects the next handler function for flow control with multiple handlers. | `srv.on("READ", "Books", async(req, next) => ... )` | 596 | | `@Locale()` | `onRead(@Locale() locale: string)` | Injects the locale of the current requesting user. | `srv.on("READ", "Books", async(req) => const locale = req.user.locale)` | 597 | | `@User()` | `onRead(@User() user: IUser)` | Injects the custom user object that is created via the `UserChecker` implementation. | `N/A` | 598 | 599 | ## 4. Example 600 | 601 | For a complete example checkout the `./test` directory which contains the project I am testing with. 602 | 603 | Additionally you can use my cds-routing-handler [Postman](https://www.getpostman.com/) collection which contains definitions for every endpoint from the project. 604 | 605 | ## 5. Bugs and Features 606 | 607 | Please open a issue when you encounter any bugs 🐞 or if you have an idea for a additional feature 💡. 608 | 609 | --- 610 | 611 | ## 6. License 612 | 613 | MIT License 614 | 615 | Copyright (c) 2019 mrbandler 616 | 617 | Permission is hereby granted, free of charge, to any person obtaining a copy 618 | of this software and associated documentation files (the "Software"), to deal 619 | in the Software without restriction, including without limitation the rights 620 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 621 | copies of the Software, and to permit persons to whom the Software is 622 | furnished to do so, subject to the following conditions: 623 | 624 | The above copyright notice and this permission notice shall be included in all 625 | copies or substantial portions of the Software. 626 | 627 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 628 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 629 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 630 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 631 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 632 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 633 | SOFTWARE. 634 | --------------------------------------------------------------------------------