├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CODING_RULES.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples ├── basic_start │ ├── package.json │ ├── src │ │ └── app │ │ │ ├── app.ts │ │ │ ├── controllers │ │ │ └── home.controller.ts │ │ │ └── filters │ │ │ └── app.start.ts │ ├── tsconfig.json │ └── tslint.json ├── dino_api │ ├── package.json │ ├── src │ │ └── app │ │ │ ├── app.ts │ │ │ ├── container │ │ │ └── container.ts │ │ │ ├── controllers │ │ │ ├── base.controller.ts │ │ │ ├── base.exception.controller.ts │ │ │ ├── child.controller.ts │ │ │ ├── child.exception.controller.ts │ │ │ ├── exception.controller.ts │ │ │ ├── home.controller.ts │ │ │ ├── index.controller.ts │ │ │ └── server.controller.ts │ │ │ ├── middlewares │ │ │ ├── base.ts │ │ │ ├── filter.ts │ │ │ └── middleware.ts │ │ │ ├── model │ │ │ └── about.model.ts │ │ │ └── services │ │ │ ├── about.service.ts │ │ │ └── date.service.ts │ ├── tsconfig.json │ └── tslint.json ├── http_verb │ ├── package.json │ ├── src │ │ └── app │ │ │ ├── app.ts │ │ │ ├── container │ │ │ └── container.ts │ │ │ └── controllers │ │ │ └── home.controller.ts │ ├── tsconfig.json │ └── tslint.json ├── index.ts ├── inversify_basic │ ├── package.json │ ├── src │ │ └── app │ │ │ ├── app.ts │ │ │ ├── container │ │ │ └── container.ts │ │ │ ├── controllers │ │ │ ├── application.error.ts │ │ │ └── home.controller.ts │ │ │ ├── model │ │ │ └── about.model.ts │ │ │ └── services │ │ │ ├── about.service.ts │ │ │ └── middleware.ts │ ├── tsconfig.json │ └── tslint.json ├── parse_api │ ├── package.json │ ├── src │ │ └── app │ │ │ ├── app.ts │ │ │ ├── controllers │ │ │ ├── about.controller.ts │ │ │ ├── home.controller.ts │ │ │ └── user.ts │ │ │ ├── middlewares │ │ │ └── filters.ts │ │ │ └── parse-handlers │ │ │ └── handlers.ts │ ├── tsconfig.json │ └── tslint.json └── task_context │ ├── package.json │ ├── src │ └── app │ │ ├── app.ts │ │ ├── container │ │ └── container.ts │ │ ├── controllers │ │ └── home.controller.ts │ │ └── services │ │ ├── middleware.ts │ │ ├── order.service.ts │ │ └── role.service.ts │ ├── tsconfig.json │ └── tslint.json ├── notes ├── INTERNAL_NOTES.md ├── RELEASES.md └── REMINDER.md ├── npm ├── .npmignore ├── NPM_PUBLISH.md ├── README.md └── package.json ├── package.json ├── spec └── support │ └── jasmine.json ├── src ├── api │ ├── attributes.ts │ ├── dino.ts │ └── index.ts ├── index.ts ├── modules │ ├── builtin │ │ ├── exceptions │ │ │ ├── exceptions.ts │ │ │ └── index.ts │ │ ├── http_response │ │ │ ├── http.response.ts │ │ │ └── index.ts │ │ ├── middlewares │ │ │ ├── action.exception.middleware.ts │ │ │ ├── dino.start.middleware.ts │ │ │ ├── http.response.exception.middleware.ts │ │ │ ├── http.response.message.middleware.ts │ │ │ ├── index.ts │ │ │ ├── response.end.middleware.ts │ │ │ ├── route.exception.middleware.ts │ │ │ ├── route.notfound.middleware.ts │ │ │ └── task.context.middleware.ts │ │ ├── parse_handlers │ │ │ ├── constants.ts │ │ │ ├── handlers.ts │ │ │ └── index.ts │ │ └── providers │ │ │ ├── index.ts │ │ │ └── user.identity.ts │ ├── constants │ │ ├── constants.ts │ │ ├── errors.ts │ │ ├── http.status.code.ts │ │ └── index.ts │ ├── controller │ │ ├── api.controller.ts │ │ ├── controller.action.ts │ │ ├── error.controller.ts │ │ └── index.ts │ ├── core │ │ ├── app.container.ts │ │ ├── dicontainer.ts │ │ ├── dino.container.ts │ │ ├── dino.controller.ts │ │ ├── dino.error.controller.ts │ │ └── index.ts │ ├── entities │ │ ├── dino.model.ts │ │ ├── dino.response.ts │ │ ├── http.response.message.ts │ │ └── index.ts │ ├── exception │ │ ├── custom.exception.ts │ │ └── index.ts │ ├── filter │ │ ├── filter.ts │ │ └── index.ts │ ├── interfaces │ │ ├── idino.ts │ │ └── index.ts │ ├── lib │ │ ├── index.ts │ │ └── reflector.ts │ ├── metadata │ │ ├── attribute.ts │ │ └── index.ts │ ├── providers │ │ ├── index.ts │ │ └── iuser.identity.ts │ ├── router │ │ ├── dino.router.ts │ │ ├── index.ts │ │ └── route.table.ts │ ├── sequence │ │ └── deferrer.ts │ ├── types │ │ ├── attribute.ts │ │ ├── dino.types.ts │ │ ├── express.ts │ │ ├── index.ts │ │ └── types.ts │ └── utility │ │ ├── data.utility.ts │ │ ├── dino.parser.ts │ │ ├── dino.utility.ts │ │ ├── function.utility.ts │ │ ├── http.utility.ts │ │ ├── index.ts │ │ ├── object.utility.ts │ │ └── route.utility.ts └── test_export │ └── index.ts ├── tests ├── index.ts ├── integration │ ├── api.controller.spec.ts │ ├── app.middleware.spec.ts │ ├── attributes.spec.ts │ ├── controller.inheritance.spec.ts │ ├── controller.middleware.spec.ts │ ├── dinowares.flow.spec.ts │ ├── error.controller.spec.ts │ ├── http.exception.spec.ts │ ├── http.response.spec.ts │ ├── index.ts │ ├── init.ts │ ├── parse.spec.ts │ ├── query.param.spec.ts │ └── segments.spec.ts └── unit │ ├── api │ ├── attributes.spec.ts │ └── dino.spec.ts │ ├── fakes │ ├── fakes.ts │ └── index.ts │ ├── index.ts │ ├── jasmine-helpers │ └── helper.ts │ ├── karma.conf.js │ └── modules │ ├── builtin │ ├── exceptions │ │ └── exceptions.spec.ts │ ├── http_response │ │ └── http.response.spec.ts │ ├── middlewares │ │ ├── action.exception.middleware.spec.ts │ │ ├── dino.start.middleware.spec.ts │ │ ├── http.exception.middleware.spec.ts │ │ ├── http.response.middleware.spec.ts │ │ ├── response.end.middleware.spec.ts │ │ ├── route.exception.middleware.spec.ts │ │ ├── route.notfound.middleware.spec.ts │ │ └── task.context.middleware.spec.ts │ ├── parse_handlers │ │ └── handlers.spec.ts │ └── providers │ │ └── user.identity.spec.ts │ ├── constants │ └── constants.spec.ts │ ├── controller │ └── controller.action.spec.ts │ ├── core │ ├── app.container.spec.ts │ ├── dicontainer.spec.ts │ ├── dino.container.spec.ts │ ├── dino.container.two.spec.ts │ ├── dino.controller.spec.ts │ └── dino.error.controller.spec.ts │ ├── metadata │ └── attribute.spec.ts │ ├── router │ ├── dino.router.spec.ts │ └── router.table.spec.ts │ ├── sequence │ └── deferrer.spec.ts │ └── utility │ ├── data.utility.spec.ts │ ├── dino.parser.spec.ts │ ├── dino.utility.spec.ts │ ├── function.utility.spec.ts │ ├── http.utility.spec.ts │ ├── object.utility.spec.ts │ └── route.utility.spec.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json ├── tslint.json └── wiki ├── README.md ├── application_middlewares.md ├── application_structure.md ├── attributes.md ├── controller_middlewares.md ├── controllers.md ├── di_setup.md ├── dino_properties.md ├── errom_vs_errorc.md ├── exceptions.md ├── faq.md ├── flow_of_dinowares.md ├── httpresponse_httpexception.md ├── installation_setup.md ├── terminology.md └── type_hinting.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*.{ts,js,json}] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your problem. 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | 22 | **Environment** 23 | * Node version: 24 | * Expressjs version: 25 | * Typescript version: 26 | * Platform: 27 | 28 | **dinoloop version: X.Y.Z** 29 | Check whether this is still an issue in the most recent dinoloop version 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # ignore mocha html reports 64 | mochawesome-report 65 | .vs 66 | 67 | public 68 | dist 69 | LICENSE 70 | package-lock.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "msjsdiag.debugger-for-chrome", 4 | "stevencl.adddoccomments", 5 | "KnisterPeter.vscode-github", 6 | "eamodio.gitlens", 7 | "eg2.tslint", 8 | "donjayamanne.githistory", 9 | "EditorConfig.editorconfig", 10 | "streetsidesoftware.code-spell-checker", 11 | "hnw.vscode-auto-open-markdown-preview", 12 | "robertohuertasm.vscode-icons", 13 | "formulahendry.code-runner" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.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": "Example.BasicStart Launch", 11 | "program": "${workspaceFolder}/examples\\basic_start\\src\\app\\app.ts", 12 | "preLaunchTask": "debugger_basicstart", 13 | "outFiles": [ 14 | "${workspaceFolder}/examples/basic_start/dist/**/*.js" 15 | ] 16 | }, 17 | { 18 | "type": "node", 19 | "request": "launch", 20 | "name": "Example.HttpVerb Launch", 21 | "program": "${workspaceFolder}/examples\\http_verb\\src\\app\\app.ts", 22 | "preLaunchTask": "debugger_httpverb", 23 | "outFiles": [ 24 | "${workspaceFolder}/examples/http_verb/dist/**/*.js" 25 | ] 26 | }, 27 | { 28 | "type": "node", 29 | "request": "launch", 30 | "name": "Example.DinoApi Launch", 31 | "program": "${workspaceFolder}/examples\\dino_api\\src\\app\\app.ts", 32 | "preLaunchTask": "debugger_dinoapi", 33 | "outFiles": [ 34 | "${workspaceFolder}/examples/dino_api/dist/**/*.js" 35 | ] 36 | }, 37 | { 38 | "type": "node", 39 | "request": "launch", 40 | "name": "Example.InversifyBasic Launch", 41 | "program": "${workspaceFolder}/examples\\inversify_basic\\src\\app\\app.ts", 42 | "preLaunchTask": "debugger_inversifybasic", 43 | "outFiles": [ 44 | "${workspaceFolder}/examples/inversify_basic/dist/**/*.js" 45 | ] 46 | }, 47 | { 48 | "type": "node", 49 | "request": "launch", 50 | "name": "Example.ParseApi Launch", 51 | "program": "${workspaceFolder}/examples\\parse_api\\src\\app\\app.ts", 52 | "preLaunchTask": "debugger_parseapi", 53 | "outFiles": [ 54 | "${workspaceFolder}/examples/parse_api/dist/**/*.js" 55 | ] 56 | }, 57 | { 58 | "type": "node", 59 | "request": "launch", 60 | "name": "Example.TaskContext Launch", 61 | "program": "${workspaceFolder}/examples\\task_context\\src\\app\\app.ts", 62 | "preLaunchTask": "debugger_taskcontext", 63 | "outFiles": [ 64 | "${workspaceFolder}/examples/task_context/dist/**/*.js" 65 | ] 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "coverage/": true, 4 | "mochawesome-report/": true, 5 | ".vs/": true, 6 | "node_modules/": true, 7 | "package-lock.json": true, 8 | "**/*/package-lock.json": true, 9 | "**/*/node_modules/": true, 10 | "**/*/dist/": true, 11 | "LICENSE": true, 12 | "dist": true, 13 | "public": true, 14 | "**/*.js": { 15 | "when": "$(basename).ts" 16 | } 17 | }, 18 | "editor.formatOnSave": true, 19 | "editor.formatOnPaste": true, 20 | "editor.tabSize": 4, 21 | "editor.insertSpaces": true, 22 | "files.insertFinalNewline": true, 23 | "files.trimFinalNewlines": true, 24 | "git.ignoreLimitWarning": true, 25 | "typescript.tsdk": "./node_modules/typescript/lib", 26 | "cSpell.words": [ 27 | "Apicontroller", 28 | "Injectable", 29 | "Karthik", 30 | "Konkula", 31 | "Truthy", 32 | "Uncomment", 33 | "basicstart", 34 | "binded", 35 | "bindmodel", 36 | "dicontainer", 37 | "dinoapi", 38 | "dinoloop", 39 | "dinowares", 40 | "eval", 41 | "exceptionfilter", 42 | "expressjs", 43 | "expresswares", 44 | "exwares", 45 | "fotos", 46 | "getall", 47 | "gmail", 48 | "httpbody", 49 | "httpstatus", 50 | "httpverb", 51 | "idino", 52 | "inversify", 53 | "inversifybasic", 54 | "iuser", 55 | "ivalidator", 56 | "jsdoc", 57 | "kjhtml", 58 | "kkonkula", 59 | "middlewares", 60 | "middlwares", 61 | "mochawesome", 62 | "multiline", 63 | "mware", 64 | "mwares", 65 | "nospace", 66 | "npmignore", 67 | "onespace", 68 | "paren", 69 | "parens", 70 | "parseapi", 71 | "preblock", 72 | "preprocessors", 73 | "quotemark", 74 | "ruri", 75 | "sampledata", 76 | "sampleuri", 77 | "setall", 78 | "singleline", 79 | "superbase", 80 | "taskcontext", 81 | "testcontroller", 82 | "testerror", 83 | "testuri", 84 | "thewillg", 85 | "typeof", 86 | "validators" 87 | ], 88 | "cSpell.enabledLanguageIds": [ 89 | "javascript", 90 | "json", 91 | "jsonc", 92 | "typescript", 93 | "yml" 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /.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 | "label": "debugger_basicstart", 8 | "type": "npm", 9 | "script": "build:example:basicstart", 10 | "problemMatcher": [] 11 | }, 12 | { 13 | "label": "debugger_httpverb", 14 | "type": "npm", 15 | "script": "build:example:httpverb", 16 | "problemMatcher": [] 17 | }, 18 | { 19 | "label": "debugger_dinoapi", 20 | "type": "npm", 21 | "script": "build:example:dinoapi", 22 | "problemMatcher": [] 23 | }, 24 | { 25 | "label": "debugger_inversifybasic", 26 | "type": "npm", 27 | "script": "build:example:inversifybasic", 28 | "problemMatcher": [] 29 | }, 30 | { 31 | "label": "debugger_parseapi", 32 | "type": "npm", 33 | "script": "build:example:parseapi", 34 | "problemMatcher": [] 35 | }, 36 | { 37 | "label": "debugger_taskcontext", 38 | "type": "npm", 39 | "script": "build:example:taskcontext", 40 | "problemMatcher": [] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /CODING_RULES.md: -------------------------------------------------------------------------------- 1 | # Coding Rules 2 | 3 | ### Guidelines 4 | 1. Dinoloop follows [this](https://stackoverflow.com/questions/469161/how-do-you-define-a-good-or-bad-api) principles. While there are situations, these principles might be discarded. 5 | 2. Do not perform null/undefined checks for every method, do it at api exposed level methods. Methods which are closer/next to api must cleanse the data and pass on to inner methods. 6 | 3. Test cases should follow 7 | 8 | `describe(path.to.test.spec.ts)` 9 | 10 | `it([methodname].[returns/throws/[other]_description_seperated_with_dashes])`. 11 | 12 | 4. Comments must be brief and self explanatory particularly on API methods. 13 | 5. API methods/properties/classes must be short, notable and easy to remember. 14 | 6. Method accepts more than 3 arguments, it is better to prefer objects. 15 | 7. There are situations where a method is made public to break down larger code into smaller logical units, to unit test properly. Those methods are not on the interface contract, make a comment at method level when it has to be done. 16 | 8. Do not invoke the constructor/new operator inside other classes/methods, it is better design to have static factory method `.create()` on a class which internally invokes new/calls the constructor. Having `.create()` static method makes easy to spy on the object creation for unit tests. 17 | 9. Do not commit package-lock.json, it is a personal choice based on [here](https://stackoverflow.com/questions/44206782/do-i-commit-the-package-lock-json-file-created-by-npm-5) and [here](https://stackoverflow.com/questions/44297803/package-lock-json-role) 18 | 10. Feel free to suggest and add best practises. 19 | 20 | 21 | That's it! Happy Coding! 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to dinoloop 2 | 3 | We would love for you to contribute to dinoloop. 4 | As a contributor, here are the guidelines we would like you to follow: 5 | 6 | ## Found a Bug 7 | If you find a bug in the source code, you can help us by 8 | [submitting an issue]. Even better, you can [submit a Pull Request] with a fix. 9 | 10 | ## Missing a Feature 11 | You can *request* a new feature or If you would like to *implement* a new feature, 12 | please submit an issue with a proposal, to be sure that we can use it. 13 | 14 | ### Submitting a Pull Request (PR) 15 | Before you submit your Pull Request (PR) consider the following guidelines: 16 | 17 | 1. Create a new branch on master branch, **including feature/bugfix changes and appropriate test cases**. 18 | 2. Follow our [CODING_RULES](https://github.com/ParallelTask/dinoloop/blob/master/CODING_RULES.md), ensure that all tests pass. 19 | 3. Commit your changes using a descriptive commit message. 20 | 4. Push your branch to GitHub 21 | 5. In GitHub, send a pull request to `dinoloop:master` 22 | * If we suggest changes then: 23 | * Make the required updates. 24 | * Rebase your branch on `master@latest` and force push to GitHub repository (this will update your Pull Request) 25 | 6. Once changes are approved, we would merge your changes to master 26 | 27 | That's it! Thank you for your contribution! 28 | 29 | #### After your pull request is merged 30 | After your pull request is merged, you can safely delete your branch. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dinoloop 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 | -------------------------------------------------------------------------------- /examples/basic_start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dinoloop-basic-start-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "npm run clean && tsc --p ./tsconfig.json", 7 | "clean": "rimraf dist", 8 | "lint": "tslint -p ./tsconfig.json -c ./tslint.json \"./src/**/*.ts*\"", 9 | "serve": "node ./dist/examples/basic_start/src/app/app.js", 10 | "start": "npm run build && npm run serve" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.18.2", 14 | "cors": "^2.8.4", 15 | "express": "^4.16.3", 16 | "reflect-metadata": "^0.1.12", 17 | "path-to-regexp": "^2.2.1" 18 | }, 19 | "devDependencies": { 20 | "@types/body-parser": "^1.16.8", 21 | "@types/express": "^4.11.1", 22 | "@types/node": "^7.0.22", 23 | "@types/reflect-metadata": "^0.1.0", 24 | "rimraf": "^2.5.2", 25 | "supertest": "^3.1.0", 26 | "tslint": "^5.8.0", 27 | "typescript": "~2.4.2" 28 | }, 29 | "engines": { 30 | "node": ">= 8.10.x", 31 | "npm": ">= 5.6.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/basic_start/src/app/app.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-require-imports 2 | import express = require('express'); 3 | // tslint:disable-next-line:no-require-imports 4 | import cors = require('cors'); 5 | // tslint:disable-next-line:no-require-imports 6 | import bodyParser = require('body-parser'); 7 | import { Dino } from '../../../index'; 8 | import { HomeController } from './controllers/home.controller'; 9 | import { 10 | InitMiddleware 11 | } from './filters/app.start'; 12 | 13 | const app = express(); 14 | const port = process.env.PORT || 8088; 15 | 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | app.use(bodyParser.json()); 18 | app.use(cors()); 19 | 20 | let dino = new Dino(app, '/api'); 21 | 22 | dino.useRouter(() => express.Router()); 23 | dino.registerController(HomeController); 24 | dino.applicationStart(InitMiddleware); 25 | dino.bind(); 26 | app.listen(port, () => console.log(`Server started on port ${port}`)); 27 | -------------------------------------------------------------------------------- /examples/basic_start/src/app/controllers/home.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller, 4 | HttpGet, 5 | HttpPost, 6 | HttpResponseException, 7 | HttpStatusCode, 8 | HttpResponseMessage 9 | } from '../../../../index'; 10 | 11 | @Controller('/home') 12 | export class HomeController extends ApiController { 13 | 14 | // verifies if built-in response middleware is working 15 | @HttpGet('/name') 16 | @HttpPost('/name') 17 | getName(): string { 18 | return 'HomeController'; 19 | } 20 | 21 | // verifies if built-in response middleware is working 22 | @HttpGet('/get') 23 | get(): any { 24 | return { 25 | data: 'testing', 26 | role: 'admin' 27 | }; 28 | } 29 | 30 | @HttpGet('/httpException') 31 | httpException(): void { 32 | throw new HttpResponseException(HttpStatusCode.internalServerError, 33 | 'TestContent'); 34 | } 35 | 36 | @HttpGet('/httpResponse') 37 | httpResponse(): HttpResponseMessage { 38 | return new HttpResponseMessage(HttpStatusCode.oK, 'TestData'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/basic_start/src/app/filters/app.start.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AppStartMiddleware 3 | } from '../../../../index'; 4 | 5 | export class InitMiddleware extends AppStartMiddleware { 6 | invoke(): void { 7 | console.log('InitMiddleware invoked'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/basic_start/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "sourceMap": true, 16 | "outDir": "./dist" 17 | }, 18 | "compileOnSave": false, 19 | "exclude": [ 20 | "dist", 21 | "node_modules" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /examples/dino_api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dinoloop-dino-api-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "npm run clean && tsc --p ./tsconfig.json", 7 | "clean": "rimraf dist", 8 | "lint": "tslint -p ./tsconfig.json -c ./tslint.json \"./src/**/*.ts*\"", 9 | "serve": "node ./dist/examples/dino_api/src/app/app.js", 10 | "start": "npm run build && npm run serve" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.18.2", 14 | "cors": "^2.8.4", 15 | "express": "^4.16.3", 16 | "injection-js": "^2.2.1", 17 | "lodash.clone": "^4.5.0", 18 | "reflect-metadata": "^0.1.12", 19 | "path-to-regexp": "^2.2.1" 20 | }, 21 | "devDependencies": { 22 | "@types/body-parser": "^1.16.8", 23 | "@types/express": "^4.11.1", 24 | "@types/node": "^7.0.22", 25 | "@types/reflect-metadata": "^0.1.0", 26 | "rimraf": "^2.5.2", 27 | "supertest": "^3.1.0", 28 | "tslint": "^5.8.0", 29 | "typescript": "~2.4.2" 30 | }, 31 | "engines": { 32 | "node": ">= 8.10.x", 33 | "npm": ">= 5.6.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/app.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-require-imports 2 | import express = require('express'); 3 | // tslint:disable-next-line:no-require-imports 4 | import cors = require('cors'); 5 | // tslint:disable-next-line:no-require-imports 6 | import bodyParser = require('body-parser'); 7 | import { ReflectiveInjector } from 'injection-js'; 8 | import * as clone from 'lodash.clone'; 9 | import { ApiController, Dino } from '../../../index'; 10 | import { HomeController } from './controllers/home.controller'; 11 | import { Container } from './container/container'; 12 | import { 13 | StartMiddlewareOne, 14 | StartMiddlewareOneAsync, 15 | StartMiddlewareTwo, 16 | ResponseMiddleware, 17 | StartMiddlewareTwoAsync, 18 | RouteNotFoundErrorMiddleware, 19 | ErrorMiddlewareOne, 20 | ErrorMiddlewareTwo, 21 | ErrorMiddlewareOneAsync 22 | } from './middlewares/middleware'; 23 | import { ServerErrorController } from './controllers/server.controller'; 24 | import { IndexController } from './controllers/index.controller'; 25 | import { ExceptionController } from './controllers/exception.controller'; 26 | import { ChildController } from './controllers/child.controller'; 27 | import { ChildExceptionController } from './controllers/child.exception.controller'; 28 | 29 | const app = express(); 30 | const port = process.env.PORT || 8088; 31 | 32 | app.use(bodyParser.urlencoded({ extended: true })); 33 | app.use(bodyParser.json()); 34 | app.use(cors()); 35 | 36 | let dino = new Dino(app, '/api'); 37 | 38 | dino.useRouter(() => express.Router()); 39 | dino.requestStart(StartMiddlewareOne); 40 | dino.requestStart(StartMiddlewareOneAsync); 41 | dino.requestStart(StartMiddlewareTwo); 42 | dino.requestStart(StartMiddlewareTwoAsync); 43 | dino.requestEnd(ResponseMiddleware); 44 | dino.registerController(HomeController); 45 | dino.registerController(IndexController); 46 | dino.registerController(ExceptionController); 47 | dino.registerController(ChildController); 48 | dino.registerController(ChildExceptionController); 49 | dino.serverError(RouteNotFoundErrorMiddleware); 50 | dino.serverError(ErrorMiddlewareOne); 51 | dino.serverError(ErrorMiddlewareOneAsync); 52 | dino.serverError(ErrorMiddlewareTwo); 53 | dino.registerApplicationError(ServerErrorController); 54 | 55 | dino.dependencyResolver(Container, 56 | (injector, type) => { 57 | let t = injector.get(type); 58 | if (t instanceof ApiController) { 59 | return clone(t); 60 | } 61 | 62 | return t; 63 | }); 64 | 65 | dino.bind(); 66 | app.listen(port, () => console.log(`Server started on port ${port}`)); 67 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/container/container.ts: -------------------------------------------------------------------------------- 1 | import { ReflectiveInjector } from 'injection-js'; 2 | import { HomeController } from '../controllers/home.controller'; 3 | import { 4 | StartMiddlewareOne, 5 | StartMiddlewareTwo, 6 | StartMiddlewareTwoAsync, 7 | StartMiddlewareOneAsync, 8 | ResponseMiddleware, 9 | RouteNotFoundErrorMiddleware, 10 | ErrorMiddlewareOne, 11 | ErrorMiddlewareTwo, 12 | ErrorMiddlewareOneAsync 13 | } from '../middlewares/middleware'; 14 | import { IAboutService, AboutService } from '../services/about.service'; 15 | import { IDateService, DateService } from '../services/date.service'; 16 | import { ServerErrorController } from '../controllers/server.controller'; 17 | import { 18 | RequestMiddleware, 19 | RequestMiddlewareAsync, 20 | RequestFilter, 21 | LogFilterAsync, 22 | NExceptionFilter, 23 | NExceptionFilterAsync, 24 | ResultFilterOneAsync, 25 | ResultFilterOne 26 | } from '../middlewares/filter'; 27 | import { IndexController } from '../controllers/index.controller'; 28 | import { ExceptionController } from '../controllers/exception.controller'; 29 | import { 30 | BaseRequestMiddleware, 31 | BaseRequestMiddlewareAsync, 32 | BaseRequestFilter, 33 | BaseRequestFilterAsync, 34 | BaseExceptionFilter, 35 | BaseExceptionFilterAsync, 36 | BaseResultFilterOne, 37 | BaseResultFilterOneAsync 38 | } from '../middlewares/base'; 39 | import { ChildController } from '../controllers/child.controller'; 40 | import { ChildExceptionController } from '../controllers/child.exception.controller'; 41 | 42 | // Define all the services and their implementations to be injected at runtime 43 | // Note that we are using angular di container as a wrapper for dependency injection 44 | const injector = ReflectiveInjector.resolveAndCreate([ 45 | 46 | // Register the controllers 47 | { provide: HomeController, useClass: HomeController }, 48 | { provide: ServerErrorController, useClass: ServerErrorController }, 49 | { provide: IndexController, useClass: IndexController }, 50 | { provide: ExceptionController, useClass: ExceptionController }, 51 | { provide: ChildController, useClass: ChildController }, 52 | { provide: ChildExceptionController, useClass: ChildExceptionController }, 53 | 54 | // Register the services 55 | { provide: IAboutService, useClass: AboutService }, 56 | { provide: IDateService, useClass: DateService }, 57 | 58 | // Register the action filters 59 | { provide: StartMiddlewareOne, useClass: StartMiddlewareOne }, 60 | { provide: StartMiddlewareTwo, useClass: StartMiddlewareTwo }, 61 | { provide: StartMiddlewareOneAsync, useClass: StartMiddlewareOneAsync }, 62 | { provide: StartMiddlewareTwoAsync, useClass: StartMiddlewareTwoAsync }, 63 | { provide: ResponseMiddleware, useClass: ResponseMiddleware }, 64 | { provide: RouteNotFoundErrorMiddleware, useClass: RouteNotFoundErrorMiddleware }, 65 | { provide: ErrorMiddlewareOne, useClass: ErrorMiddlewareOne }, 66 | { provide: ErrorMiddlewareOneAsync, useClass: ErrorMiddlewareOneAsync }, 67 | { provide: ErrorMiddlewareTwo, useClass: ErrorMiddlewareTwo }, 68 | { provide: RequestMiddleware, useClass: RequestMiddleware }, 69 | { provide: RequestMiddlewareAsync, useClass: RequestMiddlewareAsync }, 70 | { provide: RequestFilter, useClass: RequestFilter }, 71 | { provide: LogFilterAsync, useClass: LogFilterAsync }, 72 | { provide: NExceptionFilter, useClass: NExceptionFilter }, 73 | { provide: NExceptionFilterAsync, useClass: NExceptionFilterAsync }, 74 | { provide: ResultFilterOne, useClass: ResultFilterOne }, 75 | { provide: ResultFilterOneAsync, useClass: ResultFilterOneAsync }, 76 | { provide: BaseRequestMiddleware, useClass: BaseRequestMiddleware }, 77 | { provide: BaseRequestMiddlewareAsync, useClass: BaseRequestMiddlewareAsync }, 78 | { provide: BaseRequestFilter, useClass: BaseRequestFilter }, 79 | { provide: BaseRequestFilterAsync, useClass: BaseRequestFilterAsync }, 80 | { provide: BaseExceptionFilter, useClass: BaseExceptionFilter }, 81 | { provide: BaseExceptionFilterAsync, useClass: BaseExceptionFilterAsync }, 82 | { provide: BaseResultFilterOne, useClass: BaseResultFilterOne }, 83 | { provide: BaseResultFilterOneAsync, useClass: BaseResultFilterOneAsync } 84 | ]); 85 | 86 | export { injector as Container }; 87 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/controllers/base.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller, 4 | HttpGet 5 | } from '../../../../index'; 6 | import { 7 | BaseRequestMiddleware, 8 | BaseRequestMiddlewareAsync, 9 | BaseRequestFilter, 10 | BaseRequestFilterAsync 11 | } from '../middlewares/base'; 12 | 13 | @Controller('/base', { 14 | use: [ 15 | (req, res, next) => { res.locals.data.push('This is base-express-ware-one'); next(); }, 16 | (req, res, next) => { res.locals.data.push('This is base-express-ware-two'); next(); } 17 | ], 18 | middlewares: [ 19 | BaseRequestMiddleware, 20 | { 21 | useClass: BaseRequestMiddlewareAsync, 22 | data: { role: 'base-middleware-async-admin' } 23 | }], 24 | filters: [ 25 | BaseRequestFilter, 26 | { 27 | useClass: BaseRequestFilterAsync, 28 | data: { role: 'base-filter-async-admin' } 29 | } 30 | ] 31 | }) 32 | export class BaseController extends ApiController { 33 | 34 | @HttpGet('/name') 35 | get(): string { 36 | return 'BaseController'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/controllers/base.exception.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller 4 | } from '../../../../index'; 5 | import { 6 | BaseExceptionFilter, 7 | BaseExceptionFilterAsync, 8 | BaseResultFilterOne, 9 | BaseResultFilterOneAsync 10 | } from '../middlewares/base'; 11 | 12 | @Controller('/base-ex', { 13 | exceptions: [ 14 | BaseExceptionFilter, 15 | BaseExceptionFilterAsync 16 | ], 17 | result: [ 18 | BaseResultFilterOne, 19 | { 20 | useClass: BaseResultFilterOneAsync, 21 | data: { role: 'async-base-result-filter-admin' } 22 | } 23 | ] 24 | }) 25 | export class BaseExceptionController extends ApiController { } 26 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/controllers/child.controller.ts: -------------------------------------------------------------------------------- 1 | import { BaseController } from './base.controller'; 2 | import { Controller, HttpGet } from '../../../../index'; 3 | import { 4 | RequestMiddleware, 5 | RequestFilter, 6 | LogFilterAsync, 7 | RequestMiddlewareAsync 8 | } from '../middlewares/filter'; 9 | 10 | @Controller('/child', { 11 | use: [ 12 | (req, res, next) => { res.locals.data.push('This is child-express-ware-one'); next(); }, 13 | (req, res, next) => { res.locals.data.push('This is child-express-ware-two'); next(); } 14 | ], 15 | middlewares: [ 16 | RequestMiddleware, 17 | { 18 | useClass: RequestMiddlewareAsync, 19 | data: { role: 'child-middleware-async-admin' } 20 | } 21 | ], 22 | filters: [RequestFilter, LogFilterAsync] 23 | }) 24 | export class ChildController extends BaseController { 25 | 26 | // demonstrates the firing of all filters decorated on controller level 27 | @HttpGet('/filters') 28 | filters(): string[] { 29 | return this.response.locals.data; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/controllers/child.exception.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | HttpGet 4 | } from '../../../../index'; 5 | import { 6 | NExceptionFilter, 7 | NExceptionFilterAsync, 8 | ResultFilterOne, 9 | ResultFilterOneAsync 10 | } from '../middlewares/filter'; 11 | import { BaseExceptionController } from './base.exception.controller'; 12 | 13 | @Controller('/child', { 14 | exceptions: [ 15 | NExceptionFilter, 16 | NExceptionFilterAsync 17 | ], 18 | result: [ 19 | ResultFilterOneAsync, 20 | { 21 | useClass: ResultFilterOne, 22 | data: { role: 'result-child-filter-admin' } 23 | } 24 | ] 25 | }) 26 | export class ChildExceptionController extends BaseExceptionController { 27 | 28 | @HttpGet('/result-filter') 29 | filters(): string[] { 30 | return this.response.locals.data; 31 | } 32 | 33 | @HttpGet('/n-exception') 34 | get(): string { 35 | throw new Error('NExceptionFilter thrown'); 36 | } 37 | 38 | @HttpGet('/n-exception-async') 39 | getN(): string { 40 | throw new Error('NExceptionFilterAsync thrown'); 41 | } 42 | 43 | @HttpGet('/base-exception') 44 | getBase(): string { 45 | throw new Error('BaseExceptionFilter thrown'); 46 | } 47 | 48 | @HttpGet('/base-exception-async') 49 | getBaseException(): string { 50 | throw new Error('BaseExceptionFilterAsync thrown'); 51 | } 52 | 53 | @HttpGet('/exception-one') 54 | getTestExceptionOne(): string { 55 | throw new Error('SyncTestExceptionOne is thrown'); 56 | } 57 | 58 | @HttpGet('/exception-global') 59 | globalException(): string { 60 | throw new Error('GlobalTestException is thrown'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/controllers/exception.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller, 4 | HttpGet 5 | } from '../../../../index'; 6 | import { 7 | NExceptionFilter, 8 | NExceptionFilterAsync, 9 | ResultFilterOne, 10 | ResultFilterOneAsync 11 | } from '../middlewares/filter'; 12 | 13 | @Controller('/exception', { 14 | exceptions: [ 15 | NExceptionFilter, 16 | NExceptionFilterAsync 17 | ], 18 | result: [ 19 | ResultFilterOne, 20 | ResultFilterOneAsync, 21 | { 22 | useClass: ResultFilterOne, 23 | data: { role: 'result-filter-admin' } 24 | }, 25 | { 26 | useClass: ResultFilterOneAsync, 27 | data: { role: 'async-result-filter-admin' } 28 | } 29 | ] 30 | }) 31 | export class ExceptionController extends ApiController { 32 | 33 | @HttpGet('/result-filter') 34 | filters(): string[] { 35 | return this.response.locals.data; 36 | } 37 | 38 | @HttpGet('/n-exception') 39 | get(): string { 40 | throw new Error('NExceptionFilter thrown'); 41 | } 42 | 43 | @HttpGet('/n-exception-async') 44 | getN(): string { 45 | throw new Error('NExceptionFilterAsync thrown'); 46 | } 47 | 48 | @HttpGet('/exception-one') 49 | getTestExceptionOne(): string { 50 | throw new Error('SyncTestExceptionOne is thrown'); 51 | } 52 | 53 | @HttpGet('/exception-global') 54 | globalException(): string { 55 | throw new Error('GlobalTestException is thrown'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/controllers/home.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller, 4 | HttpGet, 5 | SendsResponse, 6 | Async, 7 | Deferrer, 8 | Returns 9 | } from '../../../../index'; 10 | import { IAboutService } from '../services/about.service'; 11 | import { About } from '../model/about.model'; 12 | 13 | @Controller('/home') 14 | export class HomeController extends ApiController { 15 | 16 | private name: string; 17 | 18 | constructor (private aboutService: IAboutService) { 19 | super(); 20 | this.name = 'HomeController'; 21 | } 22 | 23 | // demonstrates the firing of all request-start and request-end middlewares 24 | // also verifies if constructor is being invoked 25 | @HttpGet('/filters') 26 | get(): string[] { 27 | this.response.locals.data.push(this.name); 28 | 29 | return this.response.locals.data; 30 | } 31 | 32 | // verifies if .proceed is working as expected 33 | @SendsResponse() 34 | @HttpGet('/image/:id') 35 | imageId(id: string): void { 36 | setTimeout(() => { 37 | this.dino.proceed(`Value of id is ${id}`); 38 | }, 100); 39 | } 40 | 41 | // verifies if .throw is working as expected 42 | @SendsResponse() 43 | @HttpGet('/dino-throw') 44 | imageThrow(id: string): void { 45 | setTimeout(() => { 46 | this.dino.throw(new Error('SyncTestExceptionOne is thrown')); 47 | }, 100); 48 | } 49 | 50 | // verifies if err middlewareOne is handled 51 | @HttpGet('/sync-error-one') 52 | syncThrowOne(): string { 53 | throw new Error('SyncTestExceptionOne is thrown'); 54 | } 55 | 56 | // verifies if err middlewareTwo is handled 57 | @HttpGet('/sync-error-two') 58 | syncThrowTwo(): string { 59 | throw new Error('SyncTestExceptionTwo is thrown'); 60 | } 61 | 62 | // verifies if error-controller is fired 63 | @HttpGet('/error-controller') 64 | errorController(): string { 65 | throw new Error('ErrorController must handle this'); 66 | } 67 | 68 | // verifies if async-await is working 69 | @Async() 70 | @HttpGet('/async-data') 71 | async getAsyncData(): Promise { 72 | return await Deferrer.run((resolve, reject) => { 73 | setTimeout(() => resolve('This is AsyncData'), 10); 74 | }); 75 | } 76 | 77 | // verifies if async-await working via service 78 | @Async() 79 | @HttpGet('/await-about') 80 | async getViaAwait(): Promise { 81 | return await this.aboutService.getViaPromise(); 82 | } 83 | 84 | // verifies err handling in async-await pattern 85 | @Async() 86 | @HttpGet('/async-throw') 87 | async getUsersThrow(): Promise { 88 | throw new Error('Synchronous exception thrown with @Async Attribute'); 89 | } 90 | 91 | // verifies err handling in async-await pattern 92 | @Async() 93 | @HttpGet('/promise-error') 94 | async getViaPromiseError(): Promise { 95 | return await this.aboutService.getViaPromiseError(); 96 | } 97 | 98 | // verifies returns attribute 99 | @Returns({ schema: '$type' }) 100 | @HttpGet('/returns') 101 | getReturnsData(): string { 102 | return 'ReturnsData'; 103 | } 104 | 105 | @Async() 106 | @Returns({ schema: '$asyncType' }) 107 | @HttpGet('/returnsAsync') 108 | async getReturnsAsyncData(): Promise { 109 | return await Deferrer.run((resolve, reject) => { 110 | setTimeout(() => resolve('ReturnsAsyncData'), 10); 111 | }); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/controllers/index.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller, 4 | HttpGet 5 | } from '../../../../index'; 6 | import { 7 | RequestMiddleware, 8 | RequestMiddlewareAsync, 9 | RequestFilter, 10 | LogFilterAsync 11 | } from '../middlewares/filter'; 12 | 13 | @Controller('/index', { 14 | use: [ 15 | (req, res, next) => { res.locals.data.push('This is express-ware-one'); next(); }, 16 | (req, res, next) => { res.locals.data.push('This is express-ware-two'); next(); } 17 | ], 18 | middlewares: [ 19 | RequestMiddleware, 20 | RequestMiddlewareAsync, 21 | { 22 | useClass: RequestMiddleware, 23 | data: { role: 'middleware-admin' } 24 | }, 25 | { 26 | useClass: RequestMiddlewareAsync, 27 | data: { role: 'async-middleware-admin' } 28 | } 29 | ], 30 | filters: [ 31 | RequestFilter, 32 | LogFilterAsync, 33 | { 34 | useClass: RequestFilter, 35 | data: { role: 'filter-admin' } 36 | }, 37 | { 38 | useClass: LogFilterAsync, 39 | data: { role: 'async-filter-admin' } 40 | } 41 | ] 42 | }) 43 | export class IndexController extends ApiController { 44 | 45 | @HttpGet('/filters') 46 | get(): string { 47 | return this.response.locals.data; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/controllers/server.controller.ts: -------------------------------------------------------------------------------- 1 | import { ErrorController } from '../../../../index'; 2 | 3 | // When any internal server error occurs, this would be the last err handler. 4 | // so make sure to return the custom response 5 | export class ServerErrorController extends ErrorController { 6 | // This is the default method executes on 500 error 7 | internalServerError(): void { 8 | this.response.json({ 9 | errMsg: 'Something went wrong while processing the request. Please try after sometime', 10 | status: false, 11 | errors: [this.error.message, this.error.name, this.error.stack], 12 | locals: this.response.locals.data 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/model/about.model.ts: -------------------------------------------------------------------------------- 1 | export class About { 2 | _id?: string; 3 | title?: string = 'I_am_the_title'; 4 | content?: string = 'I_am_the_content'; 5 | createdDate?: string; 6 | modified?: [{ 7 | name?: string; 8 | timestamp?: string; 9 | action?: string; 10 | fields?: string[]; 11 | }]; 12 | isDeleted?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/services/about.service.ts: -------------------------------------------------------------------------------- 1 | import { About } from '../model/about.model'; 2 | import { Deferrer } from '../../../../index'; 3 | import { Injectable } from 'injection-js'; 4 | import { IDateService } from './date.service'; 5 | 6 | export abstract class IAboutService { 7 | abstract async getViaPromise(): Promise; 8 | abstract async getViaPromiseError(): Promise; 9 | } 10 | 11 | @Injectable() 12 | export class AboutService implements IAboutService { 13 | 14 | constructor(private dateService: IDateService) { } 15 | 16 | async getViaPromise(): Promise { 17 | return await Deferrer.run((resolve, reject) => { 18 | setTimeout(() => resolve([ 19 | new About(), 20 | new About() 21 | ]), 10); 22 | }); 23 | } 24 | 25 | async getViaPromiseError(): Promise { 26 | return await Deferrer.run((resolve, reject) => { 27 | setTimeout(() => reject(new Error('TestThrown from AboutService: getViaPromiseError()')), 10); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/dino_api/src/app/services/date.service.ts: -------------------------------------------------------------------------------- 1 | export abstract class IDateService { 2 | abstract UTCString(): string; 3 | } 4 | 5 | export class DateService implements IDateService { 6 | 7 | UTCString(): string { 8 | return new Date().toUTCString(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/dino_api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "sourceMap": true, 16 | "outDir": "./dist" 17 | }, 18 | "compileOnSave": false, 19 | "exclude": [ 20 | "dist", 21 | "node_modules" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /examples/http_verb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dinoloop-http-verb-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "npm run clean && tsc --p ./tsconfig.json", 7 | "clean": "rimraf dist", 8 | "lint": "tslint -p ./tsconfig.json -c ./tslint.json \"./src/**/*.ts*\"", 9 | "serve": "node ./dist/examples/http_verb/src/app/app.js", 10 | "start": "npm run build && npm run serve" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.18.2", 14 | "cors": "^2.8.4", 15 | "express": "^4.16.3", 16 | "injection-js": "^2.2.1", 17 | "lodash.clone": "^4.5.0", 18 | "reflect-metadata": "^0.1.12", 19 | "path-to-regexp": "^2.2.1" 20 | }, 21 | "devDependencies": { 22 | "@types/body-parser": "^1.16.8", 23 | "@types/express": "^4.11.1", 24 | "@types/node": "^7.0.22", 25 | "@types/reflect-metadata": "^0.1.0", 26 | "rimraf": "^2.5.2", 27 | "supertest": "^3.1.0", 28 | "tslint": "^5.8.0", 29 | "typescript": "~2.4.2" 30 | }, 31 | "engines": { 32 | "node": ">= 8.10.x", 33 | "npm": ">= 5.6.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/http_verb/src/app/app.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-require-imports 2 | import express = require('express'); 3 | // tslint:disable-next-line:no-require-imports 4 | import cors = require('cors'); 5 | // tslint:disable-next-line:no-require-imports 6 | import bodyParser = require('body-parser'); 7 | import { ReflectiveInjector } from 'injection-js'; 8 | import * as clone from 'lodash.clone'; 9 | import { ApiController, Dino } from '../../../index'; 10 | import { HomeController } from './controllers/home.controller'; 11 | import { Container } from './container/container'; 12 | 13 | const app = express(); 14 | const port = process.env.PORT || 8088; 15 | 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | app.use(bodyParser.json()); 18 | app.use(cors()); 19 | 20 | let dino = new Dino(app, '/api'); 21 | 22 | dino.useRouter(() => express.Router()); 23 | dino.registerController(HomeController); 24 | 25 | dino.dependencyResolver(Container, 26 | (injector, type) => { 27 | let t = injector.get(type); 28 | if (t instanceof ApiController) { 29 | return clone(t); 30 | } 31 | 32 | return t; 33 | }); 34 | 35 | dino.bind(); 36 | app.listen(port, () => console.log(`Server started on port ${port}`)); 37 | -------------------------------------------------------------------------------- /examples/http_verb/src/app/container/container.ts: -------------------------------------------------------------------------------- 1 | import { ReflectiveInjector } from 'injection-js'; 2 | import { HomeController } from '../controllers/home.controller'; 3 | 4 | // Define all the services and their implementations to be injected at runtime 5 | // Note that we are using angular di container as a wrapper for dependency injection 6 | const injector = ReflectiveInjector.resolveAndCreate([ 7 | 8 | // Register the controllers 9 | { provide: HomeController, useClass: HomeController } 10 | 11 | // Register the services 12 | // Register the action filters 13 | ]); 14 | 15 | export { injector as Container }; 16 | -------------------------------------------------------------------------------- /examples/http_verb/src/app/controllers/home.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller, 4 | HttpGet, 5 | SendsResponse, 6 | HttpPost, 7 | HttpAll 8 | } from '../../../../index'; 9 | 10 | @Controller('/home') 11 | export class HomeController extends ApiController { 12 | 13 | // verifies if get is working 14 | @SendsResponse() 15 | @HttpGet('/name') 16 | getName(): void { 17 | this.response.json('HomeController'); 18 | } 19 | 20 | // verifies if named segments are working 21 | @SendsResponse() 22 | @HttpGet('/get/:id') 23 | get(id: string): void { 24 | this.response.json(`get => params.id: ${this.request.params.id} and id: ${id}`); 25 | } 26 | 27 | // verifies if optional segments are working 28 | // Following are the routes to request 29 | // server:8088/api/home/getData/stack 30 | // id => stack 31 | // server:8088/api/home/getData 32 | // id => undefined 33 | @SendsResponse() 34 | @HttpGet('/getData/:id?') 35 | getData(id: string): void { 36 | this.response.json(`get => params.id: ${this.request.params.id} and id: ${id}`); 37 | } 38 | 39 | // verifies if named segments are working 40 | @SendsResponse() 41 | @HttpGet('/get/:id/fotos/:image') 42 | getMore(image: string, id: string): void { 43 | this.response.json(`get => params.id: ${this.request.params.id}, 44 | params.image: ${this.request.params.image} and 45 | id: ${id}, image: ${image}`); 46 | } 47 | 48 | // verifies if named segments and query-strings are mapped. 49 | // Following are the routes to request 50 | // server:8088/api/home/query/stack?search=queue 51 | // name => stack, search = queue 52 | // server:8088/api/home/query/stack?name=queue 53 | // name => stack, search = undefined (name is taken from segments and not from query string) 54 | // server:8088/api/home/query/stack?search=queue&search=list 55 | // name => stack, search = queue, list 56 | @SendsResponse() 57 | @HttpGet('/query/:name') 58 | query(name: string, search: string): void { 59 | this.response.json(`get => params.name: ${this.request.params.name}, 60 | and name: ${name}, search: ${search}`); 61 | } 62 | 63 | // verifies if this method responds to all http-verbs 64 | @SendsResponse() 65 | @HttpAll('/all/:id') 66 | all(id: string): void { 67 | this.response 68 | .json(`${this.request.method} => params.id: ${this.request.params.id} and id: ${id}`); 69 | } 70 | 71 | // verifies if request-body is injecting into first argument and also named segments 72 | @SendsResponse() 73 | @HttpPost('/post/:id') 74 | post(body: any, id: string): void { 75 | this.response.json(`post => params.id: ${this.request.params.id} and id: ${id}. 76 | and request.body: ${ JSON.stringify(this.request.body)}, injected args: 77 | ${ JSON.stringify(body)}`); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/http_verb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "sourceMap": true, 16 | "outDir": "./dist" 17 | }, 18 | "compileOnSave": false, 19 | "exclude": [ 20 | "dist", 21 | "node_modules" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /examples/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../src/index'; 2 | -------------------------------------------------------------------------------- /examples/inversify_basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dinoloop-inversify-basic-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "npm run clean && tsc --p ./tsconfig.json", 7 | "clean": "rimraf dist", 8 | "lint": "tslint -p ./tsconfig.json -c ./tslint.json \"./src/**/*.ts*\"", 9 | "serve": "node ./dist/examples/inversify_basic/src/app/app.js", 10 | "start": "npm run build && npm run serve" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.18.2", 14 | "cors": "^2.8.4", 15 | "express": "^4.16.3", 16 | "inversify": "^4.13.0", 17 | "reflect-metadata": "^0.1.12", 18 | "path-to-regexp": "^2.2.1" 19 | }, 20 | "devDependencies": { 21 | "@types/body-parser": "^1.16.8", 22 | "@types/express": "^4.11.1", 23 | "@types/node": "^7.0.22", 24 | "@types/reflect-metadata": "^0.1.0", 25 | "rimraf": "^2.5.2", 26 | "supertest": "^3.1.0", 27 | "tslint": "^5.8.0", 28 | "typescript": "~2.4.2" 29 | }, 30 | "engines": { 31 | "node": ">= 8.10.x", 32 | "npm": ">= 5.6.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/inversify_basic/src/app/app.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-require-imports 2 | import express = require('express'); 3 | // tslint:disable-next-line:no-require-imports 4 | import cors = require('cors'); 5 | // tslint:disable-next-line:no-require-imports 6 | import bodyParser = require('body-parser'); 7 | import { Container } from 'inversify'; 8 | import { Dino } from '../../../index'; 9 | import { HomeController } from './controllers/home.controller'; 10 | import { InversifyContainer } from './container/container'; 11 | import { StartMiddleware, ResponseMiddleware } from './services/middleware'; 12 | 13 | const app = express(); 14 | const port = process.env.PORT || 8088; 15 | 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | app.use(bodyParser.json()); 18 | app.use(cors()); 19 | 20 | let dino = new Dino(app, '/api'); 21 | 22 | dino.useRouter(() => express.Router()); 23 | dino.requestStart(StartMiddleware); 24 | dino.requestEnd(ResponseMiddleware); 25 | dino.registerController(HomeController); 26 | 27 | dino.dependencyResolver(InversifyContainer, 28 | (injector, type) => { 29 | return injector.resolve(type); 30 | }); 31 | 32 | dino.bind(); 33 | app.listen(port, () => console.log(`Server started on port ${port}`)); 34 | -------------------------------------------------------------------------------- /examples/inversify_basic/src/app/container/container.ts: -------------------------------------------------------------------------------- 1 | import { Container, decorate, injectable } from 'inversify'; 2 | import { 3 | ApiController, 4 | ErrorController, 5 | RequestStartMiddleware, 6 | RequestStartMiddlewareAsync, 7 | RequestEndMiddleware, 8 | RequestEndMiddlewareAsync, 9 | Middleware, 10 | MiddlewareAsync, 11 | ActionFilter, 12 | ActionFilterAsync, 13 | ErrorMiddleware, 14 | ErrorMiddlewareAsync, 15 | ResultFilter, 16 | ResultFilterAsync, 17 | ExceptionFilter, 18 | ExceptionFilterAsync, 19 | IUserIdentity, 20 | UserIdentity 21 | } from '../../../../index'; 22 | import { HomeController } from '../controllers/home.controller'; 23 | import { ApplicationError } from '../controllers/application.error'; 24 | import { IAboutService, AboutService } from '../services/about.service'; 25 | import { StartMiddleware, ResponseMiddleware } from '../services/middleware'; 26 | 27 | // builtin abstract classes which are to be extended, must go through decorate() 28 | // else inversify wont work because inversify expects the inherited members to have @injectable() 29 | decorate(injectable(), ApiController); 30 | decorate(injectable(), ErrorController); 31 | decorate(injectable(), RequestStartMiddleware); 32 | decorate(injectable(), RequestStartMiddlewareAsync); 33 | decorate(injectable(), RequestEndMiddleware); 34 | decorate(injectable(), RequestEndMiddlewareAsync); 35 | decorate(injectable(), Middleware); 36 | decorate(injectable(), MiddlewareAsync); 37 | decorate(injectable(), ActionFilter); 38 | decorate(injectable(), ActionFilterAsync); 39 | decorate(injectable(), ErrorMiddleware); 40 | decorate(injectable(), ErrorMiddlewareAsync); 41 | decorate(injectable(), ResultFilter); 42 | decorate(injectable(), ResultFilterAsync); 43 | decorate(injectable(), ExceptionFilter); 44 | decorate(injectable(), ExceptionFilterAsync); 45 | decorate(injectable(), IUserIdentity); 46 | decorate(injectable(), UserIdentity); 47 | 48 | let container = new Container(); 49 | container.bind(IAboutService).to(AboutService); 50 | container.bind(HomeController).toSelf(); 51 | container.bind(ApplicationError).toSelf(); 52 | container.bind(StartMiddleware).toSelf(); 53 | container.bind(ResponseMiddleware).toSelf(); 54 | 55 | export { container as InversifyContainer }; 56 | -------------------------------------------------------------------------------- /examples/inversify_basic/src/app/controllers/application.error.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from 'inversify'; 2 | import { ErrorController } from '../../../../index'; 3 | 4 | @injectable() 5 | export class ApplicationError extends ErrorController { 6 | internalServerError(): void { 7 | this.response.json({ 8 | errMsg: 'Something went wrong while processing. Please try after sometime', 9 | status: false, 10 | errors: [this.error.message, this.error.name, this.error.stack], 11 | locals: [this.response.locals.data] 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/inversify_basic/src/app/controllers/home.controller.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from 'inversify'; 2 | import { 3 | ApiController, 4 | Controller, 5 | HttpGet, 6 | SendsResponse, 7 | Async 8 | } from '../../../../index'; 9 | import { IAboutService } from '../services/about.service'; 10 | import { About } from '../model/about.model'; 11 | 12 | @injectable() 13 | @Controller('/home') 14 | export class HomeController extends ApiController { 15 | name: string; 16 | 17 | constructor(private aboutService: IAboutService) { 18 | super(); 19 | this.name = 'HomeController'; 20 | } 21 | 22 | @HttpGet('/name') 23 | get(): string { 24 | return this.name; 25 | } 26 | 27 | @Async() 28 | @HttpGet('/await-about') 29 | async getAbout(): Promise { 30 | return await this.aboutService.getViaPromise(); 31 | } 32 | 33 | @SendsResponse() 34 | @HttpGet('/image/:id') 35 | getId(id: string): void { 36 | setTimeout(() => { 37 | this.dino.proceed(`Value of id is ${id}`); 38 | }, 10); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/inversify_basic/src/app/model/about.model.ts: -------------------------------------------------------------------------------- 1 | export class About { 2 | _id?: string; 3 | title?: string = 'I_am_the_title'; 4 | content?: string = 'I_am_the_content'; 5 | createdDate?: string; 6 | modified?: [{ 7 | name?: string; 8 | timestamp?: string; 9 | action?: string; 10 | fields?: string[]; 11 | }]; 12 | isDeleted?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /examples/inversify_basic/src/app/services/about.service.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from 'inversify'; 2 | import { Deferrer } from '../../../../index'; 3 | import { About } from '../model/about.model'; 4 | 5 | @injectable() 6 | export abstract class IAboutService { 7 | abstract async getViaPromise(): Promise; 8 | abstract async getViaPromiseError(): Promise; 9 | } 10 | 11 | @injectable() 12 | export class AboutService implements IAboutService { 13 | async getViaPromise(): Promise { 14 | return await Deferrer.run((resolve, reject) => { 15 | setTimeout(() => resolve([ 16 | new About(), 17 | new About() 18 | ]), 10); 19 | }); 20 | } 21 | 22 | async getViaPromiseError(): Promise { 23 | return await Deferrer.run((resolve, reject) => { 24 | setTimeout(() => reject(new Error('TestThrown from AboutService: getViaPromiseError()')), 10); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/inversify_basic/src/app/services/middleware.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from 'inversify'; 2 | import { 3 | RequestStartMiddleware, 4 | Request, 5 | Response, 6 | NextFunction, 7 | RequestEndMiddleware 8 | } from '../../../../index'; 9 | 10 | @injectable() 11 | export class StartMiddleware extends RequestStartMiddleware { 12 | invoke(request: Request, response: Response, next: NextFunction): void { 13 | response.locals.data = []; 14 | response.locals.data.push('This is StartMiddleWareOne'); 15 | next(); 16 | } 17 | } 18 | 19 | @injectable() 20 | export class ResponseMiddleware extends RequestEndMiddleware { 21 | invoke(request: Request, response: Response, next: NextFunction, result: any): void { 22 | response.locals.data.push('This is ResponseMiddleWare'); 23 | response.json({ 24 | result: result, 25 | locals: response.locals.data 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/inversify_basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "sourceMap": true, 16 | "outDir": "./dist" 17 | }, 18 | "compileOnSave": false, 19 | "exclude": [ 20 | "dist", 21 | "node_modules" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /examples/parse_api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dinoloop-parse-api-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "npm run clean && tsc --p ./tsconfig.json", 7 | "clean": "rimraf dist", 8 | "lint": "tslint -p ./tsconfig.json -c ./tslint.json \"./src/**/*.ts*\"", 9 | "serve": "node ./dist/examples/parse_api/src/app/app.js", 10 | "start": "npm run build && npm run serve" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.18.2", 14 | "cors": "^2.8.4", 15 | "express": "^4.16.3", 16 | "injection-js": "^2.2.1", 17 | "lodash.clone": "^4.5.0", 18 | "reflect-metadata": "^0.1.12", 19 | "path-to-regexp": "^2.2.1" 20 | }, 21 | "devDependencies": { 22 | "@types/body-parser": "^1.16.8", 23 | "@types/express": "^4.11.1", 24 | "@types/node": "^7.0.22", 25 | "@types/reflect-metadata": "^0.1.0", 26 | "rimraf": "^2.5.2", 27 | "supertest": "^3.1.0", 28 | "tslint": "^5.8.0", 29 | "typescript": "~2.4.2" 30 | }, 31 | "engines": { 32 | "node": ">= 8.10.x", 33 | "npm": ">= 5.6.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/parse_api/src/app/app.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-require-imports 2 | import express = require('express'); 3 | // tslint:disable-next-line:no-require-imports 4 | import cors = require('cors'); 5 | // tslint:disable-next-line:no-require-imports 6 | import bodyParser = require('body-parser'); 7 | import { Dino } from '../../../index'; 8 | import { HomeController } from './controllers/home.controller'; 9 | import { ServerParseErrorMiddleware } from './middlewares/filters'; 10 | import { AboutController } from './controllers/about.controller'; 11 | 12 | const app = express(); 13 | const port = process.env.PORT || 8088; 14 | 15 | app.use(bodyParser.urlencoded({ extended: true })); 16 | app.use(bodyParser.json()); 17 | app.use(cors()); 18 | 19 | let dino = new Dino(app, '/api'); 20 | 21 | dino.useRouter(() => express.Router()); 22 | dino.registerController(HomeController); 23 | dino.registerController(AboutController); 24 | dino.serverError(ServerParseErrorMiddleware); 25 | dino.bind(); 26 | app.listen(port, () => console.log(`Server started on port ${port}`)); 27 | -------------------------------------------------------------------------------- /examples/parse_api/src/app/controllers/about.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller, 4 | HttpGet, 5 | Async, 6 | Parse, 7 | toInteger, 8 | toNumber, 9 | toBoolean 10 | } from '../../../../index'; 11 | import { throwException } from '../parse-handlers/handlers'; 12 | import { ControllerParseErrorMiddleware } from '../middlewares/filters'; 13 | 14 | @Controller('/about', { 15 | exceptions: [ControllerParseErrorMiddleware] 16 | }) 17 | export class AboutController extends ApiController { 18 | 19 | // If exception is thrown by @Parse, then it bubbles up error middleware chain 20 | // Response will be sent by ControllerParseErrorMiddleware 21 | @HttpGet('/parseException/:id') 22 | parseException(@Parse(throwException) id: number): any { 23 | return { 24 | message: '.jpg added to photo, and id must be converted to number' 25 | }; 26 | } 27 | 28 | // Async version 29 | // If exception is thrown by @Parse, then it bubbles up error middleware chain 30 | // Response will be sent by ControllerParseErrorMiddleware 31 | @Async() 32 | @HttpGet('/asyncParseException/:id') 33 | async asyncParseException(@Parse(throwException) id: number): Promise { 34 | return '.jpg added to photo, and id must be converted to number'; 35 | } 36 | 37 | // API: /toInteger/45 - should work 38 | // API: /toInteger/abc - throws exception 39 | @HttpGet('/toInteger/:id') 40 | toInteger(@Parse(toInteger) id: number): any { 41 | return { 42 | id: id, 43 | msg: 'Built-in Converter to integer' 44 | }; 45 | } 46 | 47 | // API: /toNumber/45.234 - should work 48 | // API: /toNumber/abc - throws exception 49 | @HttpGet('/toNumber/:id') 50 | toNumber(@Parse(toNumber) id: number): any { 51 | return { 52 | id: id, 53 | msg: 'Built-in Converter to number' 54 | }; 55 | } 56 | 57 | // API: /toBoolean/true - should work 58 | // API: /toBoolean/abc - throws exception 59 | @HttpGet('/toBoolean/:id') 60 | toBoolean(@Parse(toBoolean) id: boolean): any { 61 | return { 62 | id: id, 63 | msg: 'Built-in Converter to boolean' 64 | }; 65 | } 66 | 67 | // Uncomment this, to verify it throws compile time error 68 | // @HttpGet('/nullParse/:id') 69 | // nullParse(@Parse(null) id: number): any { 70 | // return { 71 | // message: 'will throw compilation error' 72 | // }; 73 | // } 74 | } 75 | -------------------------------------------------------------------------------- /examples/parse_api/src/app/controllers/user.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | name: 'dino'; 3 | } 4 | -------------------------------------------------------------------------------- /examples/parse_api/src/app/middlewares/filters.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ErrorMiddleware, 3 | ExceptionFilter, 4 | ActionParamException 5 | } from '../../../../index'; 6 | 7 | export class ServerParseErrorMiddleware extends ErrorMiddleware { 8 | invoke(err: Error, request, response, next): void { 9 | if (err instanceof ActionParamException) next(err); 10 | else { 11 | response.json(`${err.message} - Ended by Server level middleware`); 12 | } 13 | } 14 | } 15 | 16 | export class ControllerParseErrorMiddleware extends ExceptionFilter { 17 | invoke(err: Error, request, response, next): void { 18 | if (err instanceof ActionParamException) next(err); 19 | else { 20 | response.json(`${err.message} - Ended by Controller Exception Filter`); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/parse_api/src/app/parse-handlers/handlers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IParseHandler, 3 | IParseProps, 4 | DinoModel 5 | } from '../../../../index'; 6 | import { User } from '../controllers/user'; 7 | 8 | export const toNumberTen: IParseHandler = (props: IParseProps) => { 9 | return 10; 10 | }; 11 | 12 | export const toNumber: IParseHandler = (props: IParseProps) => { 13 | return parseInt(props.value); 14 | }; 15 | 16 | export const toJPG: IParseHandler = (props: IParseProps) => { 17 | return `${props.value}.jpg`; 18 | }; 19 | 20 | export const toUser: IParseHandler = (props: IParseProps) => { 21 | return null; 22 | }; 23 | 24 | export const toUserAdd: IParseHandler = (props: IParseProps) => { 25 | const dino: User = props.value; 26 | 27 | return `hello ${dino.name}`; 28 | }; 29 | 30 | export const throwException: IParseHandler = (props: IParseProps) => { 31 | throw Error('TestError'); 32 | }; 33 | 34 | export const convertToProvidedData: IParseHandler = (props: IParseProps) => { 35 | return props.data.name; 36 | }; 37 | 38 | export const returnProps: IParseHandler = (props: IParseProps) => { 39 | return { 40 | controllerName: props.controller.constructor.name, 41 | properties: props 42 | }; 43 | }; 44 | 45 | // You can aggregate the validation errors via keys 46 | export const numValidation: IParseHandler = 47 | (props: IParseProps, model: DinoModel) => { 48 | model.isValid = false; 49 | model.values.push({ 50 | key: props.key, 51 | value: props.value 52 | }); 53 | model.errors.push({ 54 | key: props.key, 55 | value: ['Should be Number', 'Max is 10'] 56 | }); 57 | 58 | return props.value; 59 | }; 60 | 61 | // You can aggregate the validation errors via keys 62 | export const boolValidation: IParseHandler = 63 | (props: IParseProps, model: DinoModel) => { 64 | model.isValid = false; 65 | model.values.push({ 66 | key: props.key, 67 | value: props.value 68 | }); 69 | model.errors.push({ 70 | key: props.key, 71 | value: ['Should be boolean', 'Only true is allowed'] 72 | }); 73 | 74 | return props.value; 75 | }; 76 | -------------------------------------------------------------------------------- /examples/parse_api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "sourceMap": true, 16 | "outDir": "./dist" 17 | }, 18 | "compileOnSave": false, 19 | "exclude": [ 20 | "dist", 21 | "node_modules" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /examples/task_context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dinoloop-task-context-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "npm run clean && tsc --p ./tsconfig.json", 7 | "clean": "rimraf dist", 8 | "lint": "tslint -p ./tsconfig.json -c ./tslint.json \"./src/**/*.ts*\"", 9 | "serve": "node ./dist/examples/task_context/src/app/app.js", 10 | "start": "npm run build && npm run serve" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.18.2", 14 | "cors": "^2.8.4", 15 | "express": "^4.16.3", 16 | "inversify": "^4.13.0", 17 | "reflect-metadata": "^0.1.12", 18 | "path-to-regexp": "^2.2.1" 19 | }, 20 | "devDependencies": { 21 | "@types/body-parser": "^1.16.8", 22 | "@types/express": "^4.11.1", 23 | "@types/node": "^7.0.22", 24 | "@types/reflect-metadata": "^0.1.0", 25 | "rimraf": "^2.5.2", 26 | "supertest": "^3.1.0", 27 | "tslint": "^5.8.0", 28 | "typescript": "~2.4.2" 29 | }, 30 | "engines": { 31 | "node": ">= 8.10.x", 32 | "npm": ">= 5.6.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/task_context/src/app/app.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-require-imports 2 | import express = require('express'); 3 | // tslint:disable-next-line:no-require-imports 4 | import cors = require('cors'); 5 | // tslint:disable-next-line:no-require-imports 6 | import bodyParser = require('body-parser'); 7 | import { Dino } from '../../../index'; 8 | import { HomeController } from './controllers/home.controller'; 9 | import { InversifyContainer } from './container/container'; 10 | import { TokenStartMiddleware, ResponseMiddleware } from './services/middleware'; 11 | import { Container } from 'inversify'; 12 | 13 | const app = express(); 14 | const port = process.env.PORT || 8088; 15 | 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | app.use(bodyParser.json()); 18 | app.use(cors()); 19 | 20 | let dino = new Dino(app, '/api'); 21 | dino.useRouter(() => express.Router()); 22 | dino.requestStart(TokenStartMiddleware); 23 | dino.registerController(HomeController); 24 | dino.requestEnd(ResponseMiddleware); 25 | dino.enableUserIdentity(); 26 | 27 | dino.dependencyResolver(InversifyContainer, 28 | (injector, type) => injector.resolve(type)); 29 | 30 | dino.bind(); 31 | app.listen(port, () => console.log(`Server started on port ${port}`)); 32 | -------------------------------------------------------------------------------- /examples/task_context/src/app/container/container.ts: -------------------------------------------------------------------------------- 1 | import { Container, decorate, injectable } from 'inversify'; 2 | import { 3 | ApiController, 4 | ErrorController, 5 | RequestStartMiddleware, 6 | RequestStartMiddlewareAsync, 7 | RequestEndMiddleware, 8 | RequestEndMiddlewareAsync, 9 | Middleware, 10 | MiddlewareAsync, 11 | ActionFilter, 12 | ActionFilterAsync, 13 | ErrorMiddleware, 14 | ErrorMiddlewareAsync, 15 | ResultFilter, 16 | ResultFilterAsync, 17 | ExceptionFilter, 18 | ExceptionFilterAsync, 19 | IUserIdentity, 20 | UserIdentity 21 | } from '../../../../index'; 22 | import { HomeController } from '../controllers/home.controller'; 23 | import { IOrderService, OrderService } from '../services/order.service'; 24 | import { IRoleService, RoleService } from '../services/role.service'; 25 | import { 26 | TokenStartMiddleware, 27 | LogMiddleware, 28 | ResponseMiddleware, 29 | RequestFilter, 30 | JsonFilter 31 | } from '../services/middleware'; 32 | 33 | // builtin abstract classes which are to be extended, must go through decorate() 34 | // else inversify wont work because inversify expects the inherited members to have @injectable() 35 | decorate(injectable(), ApiController); 36 | decorate(injectable(), ErrorController); 37 | decorate(injectable(), RequestStartMiddleware); 38 | decorate(injectable(), RequestStartMiddlewareAsync); 39 | decorate(injectable(), RequestEndMiddleware); 40 | decorate(injectable(), RequestEndMiddlewareAsync); 41 | decorate(injectable(), Middleware); 42 | decorate(injectable(), MiddlewareAsync); 43 | decorate(injectable(), ActionFilter); 44 | decorate(injectable(), ActionFilterAsync); 45 | decorate(injectable(), ErrorMiddleware); 46 | decorate(injectable(), ErrorMiddlewareAsync); 47 | decorate(injectable(), ResultFilter); 48 | decorate(injectable(), ResultFilterAsync); 49 | decorate(injectable(), ExceptionFilter); 50 | decorate(injectable(), ExceptionFilterAsync); 51 | decorate(injectable(), IUserIdentity); 52 | decorate(injectable(), UserIdentity); 53 | 54 | let container = new Container(); 55 | container.bind(IUserIdentity).to(UserIdentity); 56 | container.bind(IOrderService).to(OrderService); 57 | container.bind(IRoleService).to(RoleService); 58 | container.bind(HomeController).toSelf(); 59 | container.bind(TokenStartMiddleware).toSelf(); 60 | container.bind(LogMiddleware).toSelf(); 61 | container.bind(RequestFilter).toSelf(); 62 | container.bind(JsonFilter).toSelf(); 63 | container.bind(ResponseMiddleware).toSelf(); 64 | 65 | export { container as InversifyContainer }; 66 | -------------------------------------------------------------------------------- /examples/task_context/src/app/controllers/home.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiController, 3 | Controller, 4 | SendsResponse, 5 | HttpPost 6 | } from '../../../../index'; 7 | import { IOrderService } from '../services/order.service'; 8 | import { 9 | LogMiddleware, 10 | RequestFilter, 11 | JsonFilter, 12 | NException 13 | } from '../services/middleware'; 14 | import { IRoleService } from '../services/role.service'; 15 | import { injectable } from 'inversify'; 16 | 17 | @injectable() 18 | @Controller('/home', { 19 | middlewares: [LogMiddleware], 20 | filters: [RequestFilter], 21 | exceptions: [NException], 22 | result: [JsonFilter] 23 | }) 24 | export class HomeController extends ApiController { 25 | 26 | constructor( 27 | private roleService: IRoleService, 28 | private orderService: IOrderService) { 29 | super(); 30 | } 31 | 32 | @SendsResponse() 33 | @HttpPost('/name') 34 | getName(body: any): void { 35 | setTimeout(() => { 36 | this.dino.proceed({ 37 | role: this.roleService.getRole(), 38 | logged: this.orderService.loggedIn(), 39 | isAllowed: this.orderService.isAllowed() 40 | }); 41 | }, 20000); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/task_context/src/app/services/middleware.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RequestStartMiddleware, 3 | RequestEndMiddleware, 4 | Middleware, 5 | ActionFilter, 6 | ResultFilter, 7 | ExceptionFilter, 8 | IUserIdentity 9 | } from '../../../../index'; 10 | import { injectable } from 'inversify'; 11 | 12 | @injectable() 13 | export class TokenStartMiddleware extends RequestStartMiddleware { 14 | constructor(private userIdentity: IUserIdentity) { 15 | super(); 16 | } 17 | invoke(request: any, response: any, next: any): void { 18 | setTimeout(() => { 19 | this.userIdentity.set('role', `admin - ${request.body.name}`); 20 | next(); 21 | }, 20000); 22 | } 23 | } 24 | 25 | @injectable() 26 | export class ResponseMiddleware extends RequestEndMiddleware { 27 | constructor(private userIdentity: IUserIdentity) { 28 | super(); 29 | } 30 | invoke(request: any, response: any, next: any, result: any): void { 31 | this.userIdentity.set('FinalResponseFilter', `executed - ${request.body.name}`); 32 | response.json({ 33 | result: result, 34 | data: this.userIdentity 35 | }); 36 | } 37 | } 38 | 39 | @injectable() 40 | export class LogMiddleware extends Middleware { 41 | constructor(private userIdentity: IUserIdentity) { 42 | super(); 43 | } 44 | invoke(request: any, response: any, next: any, data?: any): void { 45 | this.userIdentity.set('loggedIn', { 46 | yesterday: true, 47 | name: request.body.name 48 | }); 49 | next(); 50 | } 51 | } 52 | 53 | @injectable() 54 | export class RequestFilter extends ActionFilter { 55 | constructor(private userIdentity: IUserIdentity) { 56 | super(); 57 | } 58 | beforeExecution(request: any, response: any, next: any, data?: any): void { 59 | this.userIdentity.set('allow', true); 60 | next(); 61 | } 62 | afterExecution(request: any, response: any, next: any, result: any, data?: any): void { 63 | this.userIdentity.set('afterFilter', `executed - ${request.body.name}`); 64 | next(); 65 | } 66 | } 67 | 68 | @injectable() 69 | export class JsonFilter extends ResultFilter { 70 | constructor(private userIdentity: IUserIdentity) { 71 | super(); 72 | } 73 | invoke(request: any, response: any, next: any, result: any, data?: any): void { 74 | this.userIdentity.set('JsonFilter', `executed - ${request.body.name}`); 75 | next(); 76 | } 77 | } 78 | 79 | @injectable() 80 | export class NException extends ExceptionFilter { 81 | constructor(private userIdentity: IUserIdentity) { 82 | super(); 83 | } 84 | invoke(err: Error, request: any, response: any, next: any): void { 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/task_context/src/app/services/order.service.ts: -------------------------------------------------------------------------------- 1 | import { IRoleService } from './role.service'; 2 | import { IUserIdentity } from '../../../../index'; 3 | import { injectable } from 'inversify'; 4 | 5 | export abstract class IOrderService { 6 | abstract isAllowed(): boolean; 7 | abstract loggedIn(): any; 8 | } 9 | 10 | @injectable() 11 | export class OrderService implements IOrderService { 12 | constructor( 13 | private roleService: IRoleService, 14 | private userIdentity: IUserIdentity) { 15 | 16 | } 17 | 18 | isAllowed(): boolean { 19 | return this.userIdentity.get('allow'); 20 | } 21 | 22 | loggedIn(): any { 23 | return this.roleService.getLoggedIn(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/task_context/src/app/services/role.service.ts: -------------------------------------------------------------------------------- 1 | import { IUserIdentity } from '../../../../index'; 2 | import { injectable } from 'inversify'; 3 | 4 | export abstract class IRoleService { 5 | abstract getRole(): string; 6 | abstract getLoggedIn(): any; 7 | } 8 | 9 | @injectable() 10 | export class RoleService implements IRoleService { 11 | 12 | constructor(private userIdentity: IUserIdentity) { } 13 | 14 | getRole(): string { 15 | return this.userIdentity.get('role'); 16 | } 17 | 18 | getLoggedIn(): any { 19 | return this.userIdentity.get('loggedIn'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/task_context/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "sourceMap": true, 16 | "outDir": "./dist" 17 | }, 18 | "compileOnSave": false, 19 | "exclude": [ 20 | "dist", 21 | "node_modules" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /notes/INTERNAL_NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ### KeyPoints 4 | 1. Built-in middlewares are singletons for memory optimization. 5 | 2. Result values (objects) are passed by reference, if one of your result filter mutates object the same mutated object reflects in other filters. 6 | 3. While creating spies in unit-tests, do not worry about the function args and types unless you use them. Just have empty args in your spy function returning desired value. 7 | -------------------------------------------------------------------------------- /notes/RELEASES.md: -------------------------------------------------------------------------------- 1 | # Releases Planned 2 | 3 | ### Future releases 4 | 5 | 1. Validators in IControllerAttribute to handle validation errors added by IParseHandlers. 6 | 2. Middlewares at action level. 7 | 3. Interceptors support using @InterceptBy. 8 | 4. Async version of ApplicationStart middleware. 9 | -------------------------------------------------------------------------------- /notes/REMINDER.md: -------------------------------------------------------------------------------- 1 | # Reminder 2 | 3 | ### KeyPoints 4 | 1. *IUserIdentity* is still an experimental feature which brings task-context concept into single-threaded javascript environment. Make sure the dependency objects in the resolution graph of IUserIdentity must not be singleton and those objects in the resolution graph must be dependency per resolve, otherwise data gets tampered between multiple requests. 5 | 6 | * Consider `A => B => IUserIdentity`, and `A => C => D`, Objects A, B must not be singletons since those objects are in resolution graph of *IUserIdentity* but C, D can be (singleton or anything) as you desire since those are not in resolution graph of *IUserIdentity*. 7 | -------------------------------------------------------------------------------- /npm/.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParallelTask/dinoloop/6b873a94b620fb2ab036745c7e866ab193f2c361/npm/.npmignore -------------------------------------------------------------------------------- /npm/NPM_PUBLISH.md: -------------------------------------------------------------------------------- 1 | # Publishing to NPM 2 | 3 | ### Steps to publish the package to NPM 4 | 5 | 1. Comment *express* imports and Uncomment *local* imports in `src/modules/types/express.ts` 6 | 2. Build the application `/src` to `/dist`, **npm run build**. 7 | 3. Copy *.npmignore*, *package.json* and *README.md* files from `/npm` to `/dist`. 8 | 4. Ensure package.json versioning matches with npm publishing version. 9 | 5. Login to your npm account, **npm login**. 10 | 6. After login, **npm publish**. 11 | 7. After publishing to npm registry, create git tag having the npm version say `vx.x.x` on `master:@latest` commit. 12 | (*You can do it from git prompt or visual studio*). 13 | 8. Push the tag (*git push origin tag_name*) to github repo. 14 | 15 | That's it! 16 | -------------------------------------------------------------------------------- /npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dinoloop", 3 | "version": "2.4.0", 4 | "description": "A lightweight REST API Library for building scalable Node.js server-side applications powered by Typescript", 5 | "main": "index.js", 6 | "typings": "index.d.ts", 7 | "homepage": "https://github.com/ParallelTask/dinoloop/", 8 | "bugs": "https://github.com/ParallelTask/dinoloop/issues", 9 | "author": "Dinoloop Team", 10 | "license": "MIT", 11 | "contributors": [ 12 | "ParallelTask", 13 | "Will Garcia" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/ParallelTask/dinoloop.git" 18 | }, 19 | "keywords": [ 20 | "dinoloop", 21 | "node-api", 22 | "rest-api", 23 | "express", 24 | "expressjs", 25 | "framework", 26 | "mvc", 27 | "dependency-injection", 28 | "web", 29 | "rest", 30 | "restful", 31 | "node-rest", 32 | "node-rest-api", 33 | "node-express" 34 | ], 35 | "dependencies": { 36 | "reflect-metadata": "0.1.12", 37 | "path-to-regexp": "2.2.1" 38 | }, 39 | "engines": { 40 | "node": ">= 8.10.x", 41 | "npm": ">= 5.6.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dinoloop-src", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "open-source nodejs framework, completely written in typescript project designed to build REST APIs", 6 | "homepage": "https://github.com/ParallelTask/dinoloop", 7 | "bugs": "https://github.com/ParallelTask/dinoloop/issues", 8 | "author": "Dinoloop Team", 9 | "license": "MIT", 10 | "contributors": [ 11 | "Karthik Konkula ", 12 | "Will Garcia " 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ParallelTask/dinoloop.git" 17 | }, 18 | "keywords": [ 19 | "dinoloop", 20 | "api", 21 | "rest-api", 22 | "express", 23 | "framework", 24 | "web", 25 | "rest", 26 | "restful" 27 | ], 28 | "scripts": { 29 | "build:example:basicstart": "npm run build --prefix ./examples/basic_start", 30 | "build:example:dinoapi": "npm run build --prefix ./examples/dino_api", 31 | "build:example:httpverb": "npm run build --prefix ./examples/http_verb", 32 | "build:example:inversifybasic": "npm run build --prefix ./examples/inversify_basic", 33 | "build:example:parseapi": "npm run build --prefix ./examples/parse_api", 34 | "build:example:taskcontext": "npm run build --prefix ./examples/task_context", 35 | "init:jasmine": "jasmine-ts init", 36 | "build": "npm run clean && tsc --p ./tsconfig.app.json", 37 | "clean": "rimraf dist", 38 | "lint": "tslint -p ./tsconfig.app.json -c ./tslint.json \"./src/**/*.ts*\"", 39 | "lint:test": "tslint -p ./tsconfig.spec.json -c ./tslint.json \"./tests/**/*.ts*\"", 40 | "test:e2e:mocha": "mocha tests/integration/**/*.ts --require ts-node/register -p ./tsconfig.spec.json", 41 | "test:e2e:mocha:watch": "npm run test:e2e:mocha --watch-extensions ts --watch", 42 | "test:e2e": "ts-mocha -p ./tsconfig.spec.json tests/integration/**/*.ts --reporter mochawesome", 43 | "test:unit": "karma start ./tests/unit/karma.conf.js", 44 | "test": "jasmine-ts JASMINE_CONFIG_PATH=spec/support/jasmine.json", 45 | "start": "npm run build" 46 | }, 47 | "dependencies": { 48 | "path-to-regexp": "~2.2.1", 49 | "reflect-metadata": "~0.1.12" 50 | }, 51 | "devDependencies": { 52 | "@types/express": "~4.11.1", 53 | "@types/jasmine": "~3.3.9", 54 | "@types/node": "~10.0.0", 55 | "@types/path-to-regexp": "~1.7.0", 56 | "@types/reflect-metadata": "~0.1.0", 57 | "@types/supertest": "~2.0.4", 58 | "body-parser": "~1.18.3", 59 | "express": "~4.16.3", 60 | "injection-js": "~2.2.1", 61 | "jasmine": "~3.3.1", 62 | "jasmine-spec-reporter": "~4.2.1", 63 | "jasmine-ts": "~0.2.1", 64 | "karma": "~6.3.16", 65 | "karma-chrome-launcher": "~2.2.0", 66 | "karma-jasmine": "~2.0.1", 67 | "karma-jasmine-html-reporter": "~1.4.0", 68 | "karma-typescript": "~4.0.0", 69 | "mocha": "~5.2.0", 70 | "mochawesome": "~3.0.2", 71 | "moq.ts": "~2.6.1", 72 | "rimraf": "~2.5.2", 73 | "source-map-support": "~0.5.10", 74 | "supertest": "~3.1.0", 75 | "ts-mocha": "~1.2.0", 76 | "ts-node": "~6.0.5", 77 | "tslint": "~5.13.1", 78 | "typescript": "~2.8.1" 79 | }, 80 | "engines": { 81 | "node": ">= 8.10.x", 82 | "npm": ">= 5.6.0" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "tests/unit", 3 | "spec_files": [ 4 | "**/*.spec.ts" 5 | ], 6 | "helpers": [ 7 | "jasmine-helpers/**/*.ts" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false, 11 | "reporters": [ 12 | { 13 | "name": "jasmine-spec-reporter#SpecReporter" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dino'; 2 | export * from './attributes'; 3 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Everywhere else we are referring to export by directory level. 2 | // However, here export only specific types/files 3 | // because these are consumed by end user 4 | export * from './api/attributes'; 5 | export { Dino } from './api/dino'; 6 | export * from './modules/builtin/exceptions/exceptions'; 7 | export * from './modules/builtin/http_response/http.response'; 8 | export * from './modules/builtin/parse_handlers/handlers'; 9 | export * from './modules/builtin/providers/user.identity'; 10 | export * from './modules/constants/http.status.code'; 11 | export * from './modules/exception/custom.exception'; 12 | export { ApiController } from './modules/controller/api.controller'; 13 | export { ErrorController } from './modules/controller/error.controller'; 14 | export { DinoModel } from './modules/entities/dino.model'; 15 | export { DinoResponse } from './modules/entities/dino.response'; 16 | export { HttpResponseMessage } from './modules/entities/http.response.message'; 17 | export * from './modules/filter/filter'; 18 | export { IDino } from './modules/interfaces/idino'; 19 | export * from './modules/sequence/deferrer'; 20 | export * from './modules/providers/iuser.identity'; 21 | export * from './modules/types/attribute'; 22 | export * from './modules/types/express'; 23 | export { 24 | KeyValuePair, 25 | IParseProps, 26 | ModelError, 27 | IKeyValuePair 28 | } from './modules/types/types'; 29 | export { IDinoRequestEndProps } from './modules/types/dino.types'; 30 | -------------------------------------------------------------------------------- /src/modules/builtin/exceptions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exceptions'; 2 | -------------------------------------------------------------------------------- /src/modules/builtin/http_response/http.response.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponseMessage } from '../../entities'; 2 | import { HttpStatusCode } from '../../constants'; 3 | 4 | /** 5 | * Creates HttpResponseMessage with HttpStatusCode.ok (200) 6 | */ 7 | export const Ok = (content?: any) => { 8 | return new HttpResponseMessage(HttpStatusCode.oK, content); 9 | }; 10 | 11 | /** 12 | * Creates HttpResponseMessage with HttpStatusCode.noContent (204) 13 | */ 14 | export const NoContent = () => { 15 | return new HttpResponseMessage(HttpStatusCode.noContent, undefined); 16 | }; 17 | 18 | /** 19 | * Creates HttpResponseMessage with HttpStatusCode.badRequest (400) 20 | */ 21 | export const BadRequest = (content?: any) => { 22 | return new HttpResponseMessage(HttpStatusCode.badRequest, content); 23 | }; 24 | 25 | /** 26 | * Creates HttpResponseMessage with HttpStatusCode.unauthorized (401) 27 | */ 28 | export const Unauthorized = (content?: any) => { 29 | return new HttpResponseMessage(HttpStatusCode.unauthorized, content); 30 | }; 31 | 32 | /** 33 | * Creates HttpResponseMessage with HttpStatusCode.notFound (404) 34 | */ 35 | export const NotFound = (content?: any) => { 36 | return new HttpResponseMessage(HttpStatusCode.notFound, content); 37 | }; 38 | -------------------------------------------------------------------------------- /src/modules/builtin/http_response/index.ts: -------------------------------------------------------------------------------- 1 | export * from './http.response'; 2 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/action.exception.middleware.ts: -------------------------------------------------------------------------------- 1 | import { ErrorMiddleware } from '../../filter'; 2 | import { ActionParamException } from '../exceptions'; 3 | import { HttpStatusCode } from '../../constants'; 4 | 5 | /** 6 | * Built-in ActionParamException Handler 7 | */ 8 | export class ActionParamExceptionMiddleware extends ErrorMiddleware { 9 | invoke(err, request, response, next): void { 10 | if (err instanceof ActionParamException) { 11 | let ex: ActionParamException = err; 12 | response 13 | .status(HttpStatusCode.badRequest) 14 | .json({ 15 | value: ex.value, 16 | message: ex.message 17 | }); 18 | } else { 19 | next(err); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/dino.start.middleware.ts: -------------------------------------------------------------------------------- 1 | import { RequestStartMiddleware } from '../../filter'; 2 | import { Response } from '../../types'; 3 | 4 | /** 5 | * initializes dino property on express response.locals for every request start. 6 | * must be registered as first builtin RequestStart middleware 7 | */ 8 | export class DinoStartMiddleware extends RequestStartMiddleware { 9 | invoke(req, res: Response, next): void { 10 | // initialize the dino object, 11 | // this is critical for other middlewares to work 12 | res.locals.dino = {}; 13 | next(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/http.response.exception.middleware.ts: -------------------------------------------------------------------------------- 1 | import { ErrorMiddleware } from '../../filter'; 2 | import { HttpResponseException } from '../exceptions'; 3 | 4 | /** 5 | * Built-in HttpResponseException Handler 6 | */ 7 | export class HttpResponseExceptionMiddleware extends ErrorMiddleware { 8 | invoke(err, request, response, next): void { 9 | if (err instanceof HttpResponseException) { 10 | let ex: HttpResponseException = err; 11 | response 12 | .status(ex.statusCode) 13 | .json(ex.content); 14 | } else { 15 | next(err); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/http.response.message.middleware.ts: -------------------------------------------------------------------------------- 1 | import { RequestEndMiddleware } from '../../filter'; 2 | import { HttpResponseMessage } from '../../entities'; 3 | import { Response } from '../../types'; 4 | 5 | /** 6 | * Built-in HttpResponseMessage Handler 7 | */ 8 | export class HttpResponseMessageMiddleware extends RequestEndMiddleware { 9 | invoke(request, response: Response, next, result: any): void { 10 | if (result instanceof HttpResponseMessage) { 11 | response 12 | .status(result.statusCode) 13 | .json(result.content); 14 | } else { 15 | next(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dino.start.middleware'; 2 | export * from './response.end.middleware'; 3 | export * from './route.exception.middleware'; 4 | export * from './route.notfound.middleware'; 5 | export * from './task.context.middleware'; 6 | export * from './action.exception.middleware'; 7 | export * from './http.response.exception.middleware'; 8 | export * from './http.response.message.middleware'; 9 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/response.end.middleware.ts: -------------------------------------------------------------------------------- 1 | import { RequestEndMiddleware } from '../../filter'; 2 | import { DataUtility } from '../../utility'; 3 | import { Response } from '../../types'; 4 | import { HttpStatusCode } from '../../constants'; 5 | 6 | // If user did not configure requestEnd middleware to send response 7 | // then ResponseMiddleware is the last requestEnd middleware that gets fired, 8 | // sends json response by default. 9 | /** 10 | * Formats result as JSON response 11 | */ 12 | export class ResponseEndMiddleware extends RequestEndMiddleware { 13 | invoke(request, response: Response, next, result): void { 14 | 15 | if (DataUtility.isUndefined(result)) { 16 | response.status(HttpStatusCode.noContent).end(); 17 | } else { 18 | response.status(HttpStatusCode.oK).json(result); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/route.exception.middleware.ts: -------------------------------------------------------------------------------- 1 | import { ErrorMiddleware } from '../../filter'; 2 | import { RouteNotFoundException } from '../exceptions'; 3 | import { HttpStatusCode } from '../../constants'; 4 | 5 | /** 6 | * Handles RouteNotFoundException thrown by RouteNotFoundMiddleware 7 | */ 8 | export class RouteExceptionMiddleware extends ErrorMiddleware { 9 | invoke(err, request, response, next): void { 10 | if (err instanceof RouteNotFoundException) { 11 | let ex: RouteNotFoundException = err; 12 | response 13 | .status(HttpStatusCode.notFound) 14 | .json(`Cannot ${ex.httpVerb} ${ex.requestUrl}`); 15 | } else { 16 | next(err); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/route.notfound.middleware.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-require-imports 2 | import pathToRegexp = require('path-to-regexp'); 3 | import { RequestStartMiddleware } from '../../filter'; 4 | import { RouteNotFoundException } from '../exceptions'; 5 | import { Response, Request } from '../../types'; 6 | import { IRouteTable } from '../../interfaces'; 7 | 8 | // it would proceed to next handler only if valid route is matched 9 | // if valid route is not found, it fires error middleware chain 10 | /** 11 | * Compares the requested route against the registered routes 12 | * @Throws RouteNotFoundException 13 | */ 14 | export class RouteNotFoundMiddleware extends RequestStartMiddleware { 15 | private routes: RegExp[] = []; 16 | private isRouteTableLoaded = false; 17 | 18 | constructor(private routeTable: IRouteTable) { 19 | super(); 20 | } 21 | 22 | invoke(request: Request, response: Response, next): void { 23 | 24 | if (this.isRouteTableLoaded === false) { 25 | // load the routes and create UrlParser objects 26 | let routes = this.routeTable.getRoutes(); 27 | for (const route of routes) { 28 | this.routes.push(pathToRegexp(route)); 29 | } 30 | this.isRouteTableLoaded = true; 31 | } 32 | 33 | // Note: Following format should match with expression in "route.table.ts" 34 | // '/[httpVerb]_[route]' 35 | let requestUrl = 36 | `/${request.method}_${request.baseUrl}${request.path}`.toLowerCase(); 37 | let isRouteMatched = false; 38 | 39 | for (const route of this.routes) { 40 | if (route.test(requestUrl)) { 41 | isRouteMatched = true; 42 | break; 43 | } 44 | } 45 | 46 | if (isRouteMatched) { 47 | next(); 48 | } else { 49 | next(new RouteNotFoundException(request.method, 50 | `${request.baseUrl}${request.path}`)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/modules/builtin/middlewares/task.context.middleware.ts: -------------------------------------------------------------------------------- 1 | import { RequestStartMiddleware } from '../../filter'; 2 | import { UserIdentity } from '../providers'; 3 | import { IDinoProperties, Response } from '../../types'; 4 | 5 | /** 6 | * Sets context property to UserIdentity instance for every request start. 7 | * If UserPrinciple enabled, this would be second built-in RequestStart middleware 8 | */ 9 | export class TaskContextMiddleware extends RequestStartMiddleware { 10 | invoke(req, res: Response, next): void { 11 | let dinoProperties: IDinoProperties = res.locals.dino; 12 | dinoProperties.context = new UserIdentity(); 13 | next(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/builtin/parse_handlers/constants.ts: -------------------------------------------------------------------------------- 1 | export const HandlerConstants = { 2 | toInteger: 'Expected integer', 3 | toNumber: 'Expected number', 4 | toBoolean: 'Expected boolean', 5 | toRegExp: 'Expected Regexp pattern' 6 | }; 7 | 8 | // 9 | // Summary: 10 | // Contains the values of exception codes defined for ActionParamException. 11 | export enum ActionParamExceptionCode { 12 | number = 0, 13 | boolean = 1, 14 | integer = 2, 15 | regexp = 3 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/builtin/parse_handlers/handlers.ts: -------------------------------------------------------------------------------- 1 | import { IParseHandler, IParseProps } from '../../types'; 2 | import { DataUtility } from '../../utility'; 3 | import { ActionParamException } from '../exceptions'; 4 | import { HandlerConstants, ActionParamExceptionCode } from './constants'; 5 | 6 | /** 7 | * Converts the parameter to integer 8 | * @Throws ActionParamException 9 | */ 10 | export const toInteger: IParseHandler = (props: IParseProps) => { 11 | const val = DataUtility.toInteger(props.value); 12 | 13 | if (!(val.isValid)) { 14 | throw new ActionParamException( 15 | props.value, 16 | props.key, 17 | props.action, 18 | props.controller.constructor.name, 19 | ActionParamExceptionCode.integer, 20 | HandlerConstants.toInteger 21 | ); 22 | } 23 | 24 | return val.value; 25 | }; 26 | 27 | /** 28 | * Converts the parameter to number 29 | * @Throws ActionParamException 30 | */ 31 | export const toNumber: IParseHandler = (props: IParseProps) => { 32 | const val = DataUtility.toNumber(props.value); 33 | 34 | if (!(val.isValid)) { 35 | throw new ActionParamException( 36 | props.value, 37 | props.key, 38 | props.action, 39 | props.controller.constructor.name, 40 | ActionParamExceptionCode.number, 41 | HandlerConstants.toNumber 42 | ); 43 | } 44 | 45 | return val.value; 46 | }; 47 | 48 | /** 49 | * Converts the parameter to boolean 50 | * @Throws ActionParamException 51 | */ 52 | export const toBoolean: IParseHandler = (props: IParseProps) => { 53 | const val = DataUtility.toBoolean(props.value); 54 | 55 | if (!(val.isValid)) { 56 | throw new ActionParamException( 57 | props.value, 58 | props.key, 59 | props.action, 60 | props.controller.constructor.name, 61 | ActionParamExceptionCode.boolean, 62 | HandlerConstants.toBoolean 63 | ); 64 | } 65 | 66 | return val.value; 67 | }; 68 | 69 | /** 70 | * Does not perform any conversion or validation. Retrieves the original value. 71 | */ 72 | export const toValue: IParseHandler = (props: IParseProps) => { 73 | return props.value; 74 | }; 75 | 76 | /** 77 | * Validates the parameter with RegExp 78 | * @Throws ActionParamException 79 | */ 80 | export const toRegExp: IParseHandler = (props: IParseProps) => { 81 | const regex: RegExp = props.data; 82 | if (regex.test(props.value)) return props.value; 83 | throw new ActionParamException( 84 | props.value, 85 | props.key, 86 | props.action, 87 | props.controller.constructor.name, 88 | ActionParamExceptionCode.regexp, 89 | `${HandlerConstants.toRegExp} ${props.data}` 90 | ); 91 | }; 92 | -------------------------------------------------------------------------------- /src/modules/builtin/parse_handlers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './handlers'; 2 | export * from './constants'; 3 | -------------------------------------------------------------------------------- /src/modules/builtin/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.identity'; 2 | -------------------------------------------------------------------------------- /src/modules/builtin/providers/user.identity.ts: -------------------------------------------------------------------------------- 1 | import { IUserIdentity } from '../../providers'; 2 | import { DataUtility } from '../../utility'; 3 | 4 | /** 5 | * UserIdentity principal context object 6 | */ 7 | export class UserIdentity extends IUserIdentity { 8 | 9 | private data = {}; 10 | 11 | constructor() { 12 | super(); 13 | } 14 | 15 | set(key: string, val: any): void { 16 | this.data[key] = val; 17 | } 18 | 19 | get(key: string): any { 20 | return this.data[key]; 21 | } 22 | 23 | contains(key: string): boolean { 24 | return !(DataUtility.isUndefined(this.data[key]) && 25 | this.data.hasOwnProperty(key) === false); 26 | } 27 | 28 | clear(): void { 29 | this.data = {}; 30 | } 31 | 32 | remove(key: string): void { 33 | delete this.data[key]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/constants/constants.ts: -------------------------------------------------------------------------------- 1 | export const Attribute = { 2 | sendsResponse: 'sendsResponse_ActionAttribute', 3 | asyncAttr: 'async_ActionAttribute', 4 | controller: 'controller_ControllerAttribute', 5 | httpGet: 'httpGet_ActionAttribute', 6 | httpPost: 'httpPost_ActionAttribute', 7 | httpPatch: 'httpPatch_ActionAttribute', 8 | httpPut: 'httpPut_ActionAttribute', 9 | httpDelete: 'httpDelete_ActionAttribute', 10 | httpHead: 'httpHead_ActionAttribute', 11 | httpAll: 'httpAll_ActionAttribute', 12 | parse: 'parse_ParameterAttribute', 13 | returns: 'returns_ActionAttribute' 14 | }; 15 | 16 | // Currently, we are supporting the basic HTTP verbs 17 | // We can define advanced HTTP verbs later in the development 18 | export const RouteAttribute = { 19 | // keys should be identical to values of Attribute.httpGet, Attribute.httpPost ... 20 | // values must match express http-verbs, ex: app.get(), app.post() 21 | httpGet_ActionAttribute: 'get', 22 | httpPost_ActionAttribute: 'post', 23 | httpDelete_ActionAttribute: 'delete', 24 | httpPut_ActionAttribute: 'put', 25 | httpPatch_ActionAttribute: 'patch', 26 | httpHead_ActionAttribute: 'head', 27 | httpAll_ActionAttribute: 'all' 28 | }; 29 | 30 | export const Constants = { 31 | errControllerDefaultMethod: 'internalServerError' 32 | }; 33 | -------------------------------------------------------------------------------- /src/modules/constants/errors.ts: -------------------------------------------------------------------------------- 1 | export const Errors = { 2 | dinoAlreadyBinded: 'dino.bind(): Already invoked', 3 | routerNotRegistered: 'Express router is not registered with dino', 4 | baseUriInvalid: 'Invalid baseUri to mount the dino app' 5 | }; 6 | -------------------------------------------------------------------------------- /src/modules/constants/http.status.code.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Summary: 3 | // Contains the values of status codes defined for HTTP. 4 | export enum HttpStatusCode { 5 | continue = 100, 6 | switchingProtocols = 101, 7 | oK = 200, 8 | created = 201, 9 | accepted = 202, 10 | nonAuthoritativeInformation = 203, 11 | noContent = 204, 12 | resetContent = 205, 13 | partialContent = 206, 14 | multipleChoices = 300, 15 | ambiguous = 300, 16 | movedPermanently = 301, 17 | moved = 301, 18 | found = 302, 19 | redirect = 302, 20 | seeOther = 303, 21 | redirectMethod = 303, 22 | notModified = 304, 23 | useProxy = 305, 24 | unused = 306, 25 | temporaryRedirect = 307, 26 | redirectKeepVerb = 307, 27 | badRequest = 400, 28 | unauthorized = 401, 29 | paymentRequired = 402, 30 | forbidden = 403, 31 | notFound = 404, 32 | methodNotAllowed = 405, 33 | notAcceptable = 406, 34 | proxyAuthenticationRequired = 407, 35 | requestTimeout = 408, 36 | conflict = 409, 37 | gone = 410, 38 | lengthRequired = 411, 39 | preconditionFailed = 412, 40 | requestEntityTooLarge = 413, 41 | requestUriTooLong = 414, 42 | unsupportedMediaType = 415, 43 | requestedRangeNotSatisfiable = 416, 44 | expectationFailed = 417, 45 | upgradeRequired = 426, 46 | internalServerError = 500, 47 | notImplemented = 501, 48 | badGateway = 502, 49 | serviceUnavailable = 503, 50 | gatewayTimeout = 504, 51 | httpVersionNotSupported = 505 52 | } 53 | -------------------------------------------------------------------------------- /src/modules/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export * from './errors'; 3 | export * from './http.status.code'; 4 | -------------------------------------------------------------------------------- /src/modules/controller/api.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from '../types'; 2 | import { DinoResponse, DinoModel } from '../entities'; 3 | 4 | // Base class that should be extended by every controller 5 | /** 6 | * Every API Controller must extend this class 7 | */ 8 | export abstract class ApiController { 9 | /** 10 | * Express.Request 11 | */ 12 | request: Request; 13 | /** 14 | * Express.Response 15 | */ 16 | response: Response; 17 | /** 18 | * Express.NextFunction 19 | */ 20 | next: NextFunction; 21 | /** 22 | * Dinoloop properties 23 | */ 24 | dino: DinoResponse; 25 | /** 26 | * Validations set by @Parse handlers are injected into this property 27 | */ 28 | model: DinoModel; 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/controller/controller.action.ts: -------------------------------------------------------------------------------- 1 | import { IActionMethodAttribute } from '../types'; 2 | 3 | export class ControllerAction { 4 | actionAttributes?: IActionMethodAttribute; 5 | 6 | constructor(actionAttributes: IActionMethodAttribute) { 7 | this.actionAttributes = actionAttributes; 8 | } 9 | 10 | static create(actionAttributes: IActionMethodAttribute): ControllerAction { 11 | return new ControllerAction(actionAttributes); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/controller/error.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from '../types'; 2 | 3 | // This is the controller to be extended to respond internal server error globally. 4 | /** 5 | * ApplicationError controller must extend this class 6 | */ 7 | export abstract class ErrorController { 8 | /** 9 | * Express.Request 10 | */ 11 | request: Request; 12 | /** 13 | * Express.Response 14 | */ 15 | response: Response; 16 | /** 17 | * Express.NextFunction 18 | */ 19 | next: NextFunction; 20 | error: Error; 21 | /** 22 | * Invoked on application error 23 | */ 24 | abstract internalServerError(): void; 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/controller/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api.controller'; 2 | export * from './controller.action'; 3 | export * from './error.controller'; 4 | -------------------------------------------------------------------------------- /src/modules/core/app.container.ts: -------------------------------------------------------------------------------- 1 | import { Express, IRouterCallBack } from '../types'; 2 | import { DinoContainer } from './dino.container'; 3 | import { DataUtility } from '../utility'; 4 | import { 5 | DinoStartMiddleware, 6 | TaskContextMiddleware, 7 | ResponseEndMiddleware, 8 | RouteExceptionMiddleware, 9 | ActionParamExceptionMiddleware, 10 | HttpResponseExceptionMiddleware, 11 | HttpResponseMessageMiddleware 12 | } from '../builtin/middlewares'; 13 | import { IAppContainer } from '../interfaces'; 14 | 15 | export class AppContainer implements IAppContainer { 16 | private app: Express; 17 | controllers: Function[] = []; 18 | baseUri: string = ''; 19 | startMiddleware: Function[] = []; 20 | endMiddleware: Function[] = []; 21 | appStartMiddleware: Function[] = []; 22 | diContainer: any; 23 | diResolveCallback: any; 24 | errorController: Function; 25 | routeNotFoundMiddleware: Function; 26 | errorMiddleware: Function[] = []; 27 | raiseModelError = false; 28 | enableTaskContext = false; 29 | useRouter: IRouterCallBack; 30 | 31 | constructor(app: Express) { 32 | this.app = app; 33 | } 34 | 35 | build(): void { 36 | 37 | let dinoContainer = DinoContainer.create({ 38 | app: this.app, 39 | raiseModelError: this.raiseModelError, 40 | enableTaskContext: this.enableTaskContext, 41 | routerCallback: this.useRouter, 42 | baseUri: this.baseUri, 43 | diContainer: this.diContainer, 44 | diResolveCb: this.diResolveCallback 45 | }); 46 | 47 | // attach dino property to response object on every request start 48 | dinoContainer.builtInRequestStartMiddleware(DinoStartMiddleware); 49 | 50 | if (this.enableTaskContext) { 51 | dinoContainer.builtInRequestStartMiddleware(TaskContextMiddleware); 52 | } 53 | 54 | if (!DataUtility.isUndefinedOrNull(this.routeNotFoundMiddleware)) { 55 | dinoContainer.routeNotFoundMiddleware(this.routeNotFoundMiddleware); 56 | } 57 | 58 | for (const middleware of this.startMiddleware) { 59 | dinoContainer.requestStartMiddleware(middleware); 60 | } 61 | 62 | for (const controller of this.controllers) { 63 | dinoContainer.registerController(controller); 64 | } 65 | 66 | for (const middleware of this.endMiddleware) { 67 | dinoContainer.requestEndMiddleware(middleware); 68 | } 69 | 70 | // Note:- built-in RequestEndMiddleware must be registered 71 | // after registering user requestEndMiddlewares 72 | 73 | // register built-in RequestEndMiddleware 74 | dinoContainer.builtInRequestEndMiddleware(HttpResponseMessageMiddleware); 75 | 76 | // register ResponseEndMiddleware as the last built-in RequestEndMiddleware 77 | dinoContainer.builtInRequestEndMiddleware(ResponseEndMiddleware); 78 | 79 | for (const middleware of this.errorMiddleware) { 80 | dinoContainer.registerErrorMiddleware(middleware); 81 | } 82 | 83 | // Note:- built-in ErrorMiddleware must be registered 84 | // after registering user ErrorMiddlewares 85 | 86 | dinoContainer.builtInErrorMiddleware(RouteExceptionMiddleware); 87 | dinoContainer.builtInErrorMiddleware(HttpResponseExceptionMiddleware); 88 | dinoContainer.builtInErrorMiddleware(ActionParamExceptionMiddleware); 89 | 90 | // Register the application error controller 91 | // This would be the last error middleware to handle error object 92 | // make sure to register only after registering ErrorMiddleWares 93 | if (!DataUtility.isUndefinedOrNull(this.errorController)) { 94 | dinoContainer.registerErrorController(this.errorController); 95 | } 96 | 97 | // After binding to express 98 | // Start executing initialization code using appStartMiddleware 99 | for (const middleware of this.appStartMiddleware) { 100 | dinoContainer.appStartMiddleware(middleware); 101 | } 102 | } 103 | 104 | static create(app: Express): AppContainer { 105 | return new AppContainer(app); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/modules/core/dicontainer.ts: -------------------------------------------------------------------------------- 1 | import { DataUtility } from '../utility'; 2 | import { IDIContainer } from '../interfaces'; 3 | 4 | export class DIContainer implements IDIContainer { 5 | constructor(private injector: any, private cb: any) { } 6 | 7 | // resolve the component from the di container 8 | // If no resolve callback is registered, that means no DI framework is configured. 9 | // for such cases just instantiate the component with 'new operator'. 10 | // this flexibility is added to make it work without DI framework 11 | resolve(type: any): T { 12 | return (!DataUtility.isUndefinedOrNull(this.cb)) ? 13 | this.cb(this.injector, type) : new type(); 14 | } 15 | 16 | static create(injector: any, cb: any): DIContainer { 17 | return new DIContainer(injector, cb); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/core/dino.error.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from '../types'; 2 | import { ErrorController } from '../controller'; 3 | import { IDinoErrorController } from '../interfaces'; 4 | 5 | export class DinoErrorController implements IDinoErrorController { 6 | private controller: ErrorController; 7 | 8 | constructor(controller: ErrorController) { 9 | this.controller = controller; 10 | } 11 | 12 | patch(err: Error, req: Request, res: Response, next: NextFunction): void { 13 | this.controller.request = req; 14 | this.controller.response = res; 15 | this.controller.next = next; 16 | this.controller.error = err; 17 | } 18 | 19 | invoke(actionName: string): void { 20 | this.controller[actionName](); 21 | } 22 | 23 | static create(controller: ErrorController): DinoErrorController { 24 | return new DinoErrorController(controller); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.container'; 2 | export * from './dicontainer'; 3 | export * from './dino.container'; 4 | export * from './dino.controller'; 5 | export * from './dino.error.controller'; 6 | -------------------------------------------------------------------------------- /src/modules/entities/dino.model.ts: -------------------------------------------------------------------------------- 1 | import { KeyValuePair, ModelError } from '../types'; 2 | 3 | /** 4 | * Represents the model validation errors 5 | */ 6 | export class DinoModel { 7 | isValid?: boolean = true; 8 | /** 9 | * Key - Param key, Value - Param value 10 | */ 11 | values?: KeyValuePair[] = []; 12 | /** 13 | * Key - Param key, Value - List of validation errors 14 | */ 15 | errors: ModelError[] = []; 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/entities/dino.response.ts: -------------------------------------------------------------------------------- 1 | // DinoResponse is just an abstraction over IDinoProperties 2 | // IDinoProperties have all the properties which exist on res.locals.dino. 3 | // DinoResponse abstracts and exposes its subset to end consumer. 4 | // limited API is provided, not to confuse the user with IDinoProperties. 5 | export abstract class DinoResponse { 6 | /** 7 | * Proceeds to next middleware in the chain 8 | */ 9 | proceed?(result: any): void; 10 | 11 | /** 12 | * Proceeds to next error middleware in the chain 13 | */ 14 | throw?(err: Error): void; 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/entities/http.response.message.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatusCode } from '../constants'; 2 | 3 | /** 4 | * Creates HttpResponseMessage with the specified status code and value 5 | */ 6 | export class HttpResponseMessage { 7 | statusCode: HttpStatusCode; 8 | content: T; 9 | 10 | constructor(statusCode: HttpStatusCode, content?: T) { 11 | this.statusCode = statusCode; 12 | this.content = content; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dino.model'; 2 | export * from './dino.response'; 3 | export * from './http.response.message'; 4 | -------------------------------------------------------------------------------- /src/modules/exception/custom.exception.ts: -------------------------------------------------------------------------------- 1 | // All custom exceptions must extend CustomException 2 | // It supports inner-exception where as native js error object does not support inner exceptions 3 | /** 4 | * CustomExceptions must extend this Exception 5 | */ 6 | export abstract class CustomException extends Error { 7 | innerException: Error; 8 | type: string; 9 | constructor(message: string, ex?: Error) { 10 | super(message); 11 | this.innerException = ex; 12 | Object.setPrototypeOf(this, new.target.prototype); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/exception/index.ts: -------------------------------------------------------------------------------- 1 | export * from './custom.exception'; 2 | -------------------------------------------------------------------------------- /src/modules/filter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './filter'; 2 | -------------------------------------------------------------------------------- /src/modules/interfaces/idino.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IRouterCallBack, 3 | IMiddlewareProvider, 4 | Express, 5 | Request, 6 | Response, 7 | NextFunction 8 | } from '../types'; 9 | 10 | export interface IDino { 11 | registerController(controller: Function & { prototype: T }): void; 12 | registerApplicationError(type: Function & { prototype: T }): void; 13 | disableRouteNotFoundException(): void; 14 | enableUserIdentity(): void; 15 | requestStart(middleware: Function & { prototype: T }): void; 16 | requestEnd(middleware: Function & { prototype: T }): void; 17 | serverError(middleware: Function & { prototype: T }): void; 18 | applicationStart(middleware: Function & { prototype: T }): void; 19 | dependencyResolver( 20 | injector: T, cb: (injector: T, type: any) => any): void; 21 | bind(): void; 22 | useRouter(cb: () => any): void; 23 | } 24 | 25 | export interface IAppContainer { 26 | controllers: Function[]; 27 | baseUri: string; 28 | startMiddleware: Function[]; 29 | endMiddleware: Function[]; 30 | appStartMiddleware: Function[]; 31 | diContainer: any; 32 | diResolveCallback: any; 33 | errorController: Function; 34 | errorMiddleware: Function[]; 35 | routeNotFoundMiddleware: Function; 36 | raiseModelError: boolean; 37 | enableTaskContext: boolean; 38 | useRouter: IRouterCallBack; 39 | build(): void; 40 | } 41 | 42 | export interface IDIContainer { 43 | resolve(type: any): T; 44 | } 45 | 46 | export interface IDinoContainer { 47 | builtInRequestEndMiddleware(middleware: Function): void; 48 | builtInErrorMiddleware(middleware: Function): void; 49 | builtInRequestStartMiddleware(middleware: Function): void; 50 | routeNotFoundMiddleware(middleware: Function): void; 51 | appStartMiddleware(middleware: Function): void; 52 | requestStartMiddleware(middleware: Function): void; 53 | requestEndMiddleware(middleware: Function): void; 54 | registerErrorMiddleware(middleware: Function): void; 55 | registerErrorController(type: Function): void; 56 | registerController(type: Function): void; 57 | } 58 | 59 | export interface IRouteTable { 60 | add(route: string, httpVerb: string): void; 61 | getRoutes(): string[]; 62 | } 63 | 64 | export interface IDinoController { 65 | patch(req: Request, res: Response, next: NextFunction): void; 66 | invoke(actionName: string, httpVerb: string, requestUrl: string): void; 67 | invokeAsync(actionName: string, httpVerb: string, requestUrl: string): Promise; 68 | } 69 | 70 | export interface IDinoErrorController { 71 | patch(err: Error, req: Request, res: Response, next: NextFunction): void; 72 | invoke(actionName: string): void; 73 | } 74 | 75 | export interface IDinoRouter { 76 | registerMiddlewares(middlewares: IMiddlewareProvider[]): void; 77 | registerBeginActionFilters(actionFilters: IMiddlewareProvider[]): void; 78 | registerAfterActionFilters(actionFilters: IMiddlewareProvider[]): void; 79 | registerResultFilters(resultFilters: IMiddlewareProvider[]): void; 80 | registerExceptionFilters( 81 | app: Express, uri: string | RegExp, filters: IMiddlewareProvider[]): void; 82 | } 83 | -------------------------------------------------------------------------------- /src/modules/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './idino'; 2 | -------------------------------------------------------------------------------- /src/modules/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './reflector'; 2 | -------------------------------------------------------------------------------- /src/modules/lib/reflector.ts: -------------------------------------------------------------------------------- 1 | // refer: https://github.com/rbuckton/reflect-metadata 2 | import 'reflect-metadata'; 3 | 4 | export const Reflector = Reflect; 5 | -------------------------------------------------------------------------------- /src/modules/metadata/index.ts: -------------------------------------------------------------------------------- 1 | export * from './attribute'; 2 | -------------------------------------------------------------------------------- /src/modules/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './iuser.identity'; 2 | -------------------------------------------------------------------------------- /src/modules/providers/iuser.identity.ts: -------------------------------------------------------------------------------- 1 | 2 | // Injecting this service into constructor dependencies would have instance per request. 3 | // All the services resolved would have same IUserIdentity instance for the entire request. 4 | // Hence providing UserIdentity concept which is similar to C# WebApi 5 | 6 | /** 7 | * Contains information associated with request/user 8 | */ 9 | export abstract class IUserIdentity { 10 | abstract set(key: string, val: any): void; 11 | abstract get(key: string): any; 12 | abstract contains(key: string): boolean; 13 | abstract clear(): void; 14 | abstract remove(key: string): void; 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/router/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dino.router'; 2 | export * from './route.table'; 3 | -------------------------------------------------------------------------------- /src/modules/router/route.table.ts: -------------------------------------------------------------------------------- 1 | import { IRouteTable } from '../interfaces'; 2 | import { Attribute, RouteAttribute } from '../constants'; 3 | 4 | // RouteTable has the list of routes registered with dino 5 | // but these routes are registered after invoking .bind(). 6 | export class RouteTable implements IRouteTable { 7 | private routes: string[] = []; 8 | 9 | add(route: string, httpVerb: string): void { 10 | // if httpVerb is 'all', it should respond to every httpverb 11 | // we could use router's named segments to achieve this 12 | const url = 13 | httpVerb === RouteAttribute[Attribute.httpAll] ? 14 | `/:verb_${route}` : `/${httpVerb}_${route}`; 15 | this.routes.push(url.toLowerCase()); 16 | } 17 | 18 | getRoutes(): string[] { 19 | return this.routes; 20 | } 21 | 22 | static create(): RouteTable { 23 | return new RouteTable(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/sequence/deferrer.ts: -------------------------------------------------------------------------------- 1 | 2 | // Provides a wrapper over native Promise object. 3 | // use Deferrer to execute callback pattern inside promise. 4 | // Deferrer makes it easier to achieve async-await pattern using Promises 5 | /** 6 | * Deferrer provides asynchronous operation in async-await pattern 7 | */ 8 | export abstract class Deferrer { 9 | /** 10 | * Register the callback either the eventual value is resolved or rejected 11 | */ 12 | static run(cb: (resolve: (value?: T | PromiseLike) => void, 13 | reject: (reason?: Error) => void) => void): Promise { 14 | return new Promise((resolve, reject) => { 15 | cb(resolve, reject); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/types/attribute.ts: -------------------------------------------------------------------------------- 1 | import { IParseProps } from './types'; 2 | import { DinoModel } from '../entities'; 3 | 4 | export interface IMiddlewareClass { 5 | useClass?: Function; 6 | data?: any; 7 | } 8 | 9 | export declare type IMiddlewareProvider = Function | IMiddlewareClass; 10 | 11 | export interface IControllerAttribute { 12 | filters?: IMiddlewareProvider[]; 13 | middlewares?: IMiddlewareProvider[]; 14 | exceptions?: IMiddlewareProvider[]; 15 | result?: IMiddlewareProvider[]; 16 | use?: any[]; 17 | } 18 | 19 | export interface IControllerAttributeExtended extends IControllerAttribute { 20 | prefix?: string; 21 | } 22 | 23 | export interface IControllerAttributeProvider { 24 | prefix?: string; 25 | afterActionFilters?: IMiddlewareProvider[]; 26 | beforeActionFilters?: IMiddlewareProvider[]; 27 | middlewares?: IMiddlewareProvider[]; 28 | exceptions?: IMiddlewareProvider[]; 29 | result?: IMiddlewareProvider[]; 30 | use?: any[]; 31 | } 32 | 33 | export declare type IRouterCallBack = () => any; 34 | 35 | export declare type IParseHandler = 36 | (props: IParseProps, model?: DinoModel) => any; 37 | 38 | export interface IParseAttribute { 39 | key?: string; 40 | handler?: IParseHandler; 41 | controller?: any; 42 | action?: string; 43 | data?: any; 44 | isQueryParam?: boolean; 45 | paramIndex?: number; 46 | } 47 | 48 | export interface IActionMethodAttribute { 49 | route?: string; 50 | httpVerb?: string; 51 | isAsync?: boolean; 52 | sendsResponse?: boolean; 53 | actionArguments?: IParseAttribute[]; 54 | returns?: Function | object; 55 | } 56 | -------------------------------------------------------------------------------- /src/modules/types/dino.types.ts: -------------------------------------------------------------------------------- 1 | import { IRouterCallBack } from './attribute'; 2 | import { IDIContainer } from '../interfaces'; 3 | import { Express } from './express'; 4 | 5 | export interface IDinoRequestEndProps { 6 | result?: any; 7 | returns?: any; 8 | } 9 | 10 | export interface IDinoProperties extends IDinoRequestEndProps { 11 | context?: any; 12 | proceed?(result: any): void; 13 | throw?(err: Error): void; 14 | } 15 | 16 | export interface IRouterConfig { 17 | diContainer: IDIContainer; 18 | routerCb: IRouterCallBack; 19 | enableTaskContext: boolean; 20 | } 21 | 22 | export interface IDinoContainerConfig { 23 | app: Express; 24 | baseUri: string; 25 | raiseModelError: boolean; 26 | enableTaskContext: boolean; 27 | routerCallback: IRouterCallBack; 28 | diContainer: any; 29 | diResolveCb: any; 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/types/express.ts: -------------------------------------------------------------------------------- 1 | /**** Comment these exports while publishing to NPM ****/ 2 | // tslint:disable-next-line:no-implicit-dependencies 3 | import { Express, Router, Request, Response, NextFunction } from 'express'; 4 | export type Express = Express; 5 | export type Router = Router; 6 | export type Request = Request; 7 | export type Response = Response; 8 | export type NextFunction = NextFunction; 9 | /******************************************************/ 10 | 11 | /**** UnComment these exports while publishing to NPM ****/ 12 | // export type Express = any; 13 | // export type Router = any; 14 | // export type Request = any; 15 | // export type Response = any; 16 | // export type NextFunction = any; 17 | /********************************************************/ 18 | -------------------------------------------------------------------------------- /src/modules/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './attribute'; 2 | export * from './dino.types'; 3 | export * from './express'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /src/modules/types/types.ts: -------------------------------------------------------------------------------- 1 | export interface IKeyValuePair { 2 | key?: string; 3 | value?: any; 4 | } 5 | 6 | export class KeyValuePair { 7 | key: string; 8 | value: any; 9 | } 10 | 11 | export class ModelError { 12 | key: string; 13 | value: string[]; 14 | } 15 | 16 | export interface IParseProps { 17 | action?: string; 18 | controller?: any; 19 | key?: string; 20 | data?: any; 21 | value?: any; 22 | } 23 | 24 | export interface IConversionValidation { 25 | isValid: boolean; 26 | value: T; 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/utility/data.utility.ts: -------------------------------------------------------------------------------- 1 | import { IConversionValidation } from '../types'; 2 | 3 | /** 4 | * Wrapper methods to test the datatype of the given value 5 | */ 6 | export abstract class DataUtility { 7 | 8 | static isUndefined(data: any): boolean { 9 | return data === undefined; 10 | } 11 | 12 | static isUndefinedOrNull(data: any): boolean { 13 | return DataUtility.isUndefined(data) || data === null; 14 | } 15 | 16 | static isNull(data: any): boolean { 17 | return data === null; 18 | } 19 | 20 | static isEmpty(data: any): boolean { 21 | return DataUtility.isUndefinedOrNull(data) || data === ''; 22 | } 23 | 24 | static isFunction(data: any): boolean { 25 | return typeof data === 'function'; 26 | } 27 | 28 | static isString(data: any): boolean { 29 | return typeof data === 'string'; 30 | } 31 | 32 | static isRegExp(data: any): boolean { 33 | return data instanceof RegExp; 34 | } 35 | 36 | static isBoolean(data: any): boolean { 37 | const val = DataUtility.isString(data) && 38 | (data.toLowerCase() === 'false' || data.toLowerCase() === 'true'); 39 | 40 | return val ? true : DataUtility.isStrictBoolean(data); 41 | } 42 | 43 | static isStrictBoolean(data: any): boolean { 44 | return typeof data === 'boolean'; 45 | } 46 | 47 | static isNumber(data: any): boolean { 48 | return DataUtility.isString(data) ? 49 | Number.isFinite(Number(data)) : DataUtility.isStrictNumber(data); 50 | } 51 | 52 | /** 53 | * Strict - does not use string conversion 54 | */ 55 | static isStrictNumber(data: any): boolean { 56 | return typeof data === 'number'; 57 | } 58 | 59 | static isArray(data: any): boolean { 60 | return Array.isArray(data); 61 | } 62 | 63 | static isInteger(data: any): boolean { 64 | return DataUtility.isStrictNumber(data) || DataUtility.isString(data) 65 | ? Number.isInteger(Number(data)) : false; 66 | } 67 | 68 | static toNumber(data: any): IConversionValidation { 69 | let valid = false; 70 | let value: number; 71 | 72 | if (DataUtility.isNumber(data)) { 73 | value = Number.parseFloat(data); 74 | valid = true; 75 | } 76 | 77 | return { 78 | isValid: valid, 79 | value: value 80 | }; 81 | } 82 | 83 | static toInteger(data: any): IConversionValidation { 84 | let valid = false; 85 | let value: number; 86 | 87 | if (DataUtility.isStrictNumber(data) || 88 | (DataUtility.isString(data) && Number.isInteger(Number(data)))) { 89 | value = Number.parseInt(data); 90 | valid = true; 91 | } 92 | 93 | return { 94 | isValid: valid, 95 | value: value 96 | }; 97 | } 98 | 99 | static toBoolean(data: any): IConversionValidation { 100 | let valid = false; 101 | let value: boolean; 102 | 103 | if (DataUtility.isStrictBoolean(data)) { 104 | valid = true; 105 | value = data; 106 | } else if (DataUtility.isString(data)) { 107 | if (data.toLowerCase() === 'true') { 108 | valid = true; 109 | value = true; 110 | } else if (data.toLowerCase() === 'false') { 111 | valid = true; 112 | value = false; 113 | } 114 | } 115 | 116 | return { 117 | isValid: valid, 118 | value: value 119 | }; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/modules/utility/dino.parser.ts: -------------------------------------------------------------------------------- 1 | import { IMiddlewareProvider, IMiddlewareClass } from '../types'; 2 | import { DataUtility } from './data.utility'; 3 | 4 | /** 5 | * ParserUtility functions to parse dino objects 6 | */ 7 | export abstract class DinoParser { 8 | 9 | /** 10 | * Parses the MiddlewareProvider to MiddlewareClass 11 | */ 12 | static parseMiddlewareProvider(middleware: IMiddlewareProvider): IMiddlewareClass { 13 | let middlewareFunction: Function = middleware as any; 14 | let data = undefined; 15 | let middlewareClass: IMiddlewareClass = middleware as any; 16 | 17 | // if provider is non-function and has custom data, it should be using .useClass 18 | if (!DataUtility.isFunction(middleware) 19 | && !DataUtility.isUndefinedOrNull(middlewareClass)) { 20 | 21 | // data must be available only if .useClass is valid func 22 | if (DataUtility.isFunction(middlewareClass.useClass)) { 23 | middlewareFunction = middlewareClass.useClass; 24 | data = middlewareClass.data; 25 | } else { 26 | middlewareFunction = middlewareClass.useClass; 27 | } 28 | } 29 | 30 | return { 31 | data: data, 32 | useClass: middlewareFunction 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/utility/function.utility.ts: -------------------------------------------------------------------------------- 1 | import { DataUtility } from './data.utility'; 2 | 3 | let STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 4 | let ARGUMENT_NAMES = /([^\s,]+)/g; 5 | 6 | // returns only param names as string array 7 | // refer: https://stackoverflow.com/questions/1007981/how-to-get-function-parameter-names-values-dynamically 8 | export abstract class FunctionUtility { 9 | static getParamNames(func: Function): string[] { 10 | 11 | if (DataUtility.isUndefinedOrNull(func)) return []; 12 | 13 | let fnStr = func.toString().replace(STRIP_COMMENTS, ''); 14 | let result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); 15 | if (result === null) { 16 | result = []; 17 | } 18 | 19 | return result; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/utility/http.utility.ts: -------------------------------------------------------------------------------- 1 | import { RouteAttribute } from '../constants'; 2 | 3 | export abstract class HttpUtility { 4 | 5 | static hasBody(httpVerb: string): boolean { 6 | return (httpVerb === RouteAttribute.httpPost_ActionAttribute || 7 | httpVerb === RouteAttribute.httpPatch_ActionAttribute || 8 | httpVerb === RouteAttribute.httpPut_ActionAttribute) ? 9 | true : false; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/utility/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data.utility'; 2 | export * from './dino.parser'; 3 | export * from './dino.utility'; 4 | export * from './function.utility'; 5 | export * from './http.utility'; 6 | export * from './object.utility'; 7 | export * from './route.utility'; 8 | -------------------------------------------------------------------------------- /src/modules/utility/object.utility.ts: -------------------------------------------------------------------------------- 1 | import { DataUtility } from './data.utility'; 2 | 3 | /** 4 | * Wrapper methods for native js Object 5 | * Just to make sure not to inject native js functions into the API 6 | * This increases to support older versions that dont support the latest Object api 7 | */ 8 | export abstract class ObjectUtility { 9 | 10 | private static _replaceObjectReferences(obj: any, objToReplace: any, className: Function): void { 11 | ObjectUtility.keys(obj).forEach(key => { 12 | if (obj[key] instanceof className) { 13 | obj[key] = objToReplace; 14 | } else if (typeof obj[key] === 'object' && obj[key] !== null) { 15 | ObjectUtility._replaceObjectReferences(obj[key], objToReplace, className); 16 | } 17 | }); 18 | } 19 | 20 | /** 21 | * Works exactly => Object.create(obj) 22 | */ 23 | static create(obj: any): any { 24 | return Object.create(obj); 25 | } 26 | 27 | /** 28 | * Works exactly => Object.keys(obj) 29 | */ 30 | static keys(obj: any): string[] { 31 | return Object.keys(obj); 32 | } 33 | 34 | /** 35 | * Works exactly => Object.getPrototypeOf(obj) 36 | */ 37 | static getPrototypeOf(obj: any): any { 38 | return Object.getPrototypeOf(obj); 39 | } 40 | 41 | /** 42 | * Works exactly => Object.getOwnPropertyNames(obj) 43 | */ 44 | static getOwnPropertyNames(obj: any): any { 45 | return Object.getOwnPropertyNames(obj); 46 | } 47 | 48 | static replaceObjectReferences(obj: any, objToReplace: any, className: Function): any { 49 | 50 | if (DataUtility.isUndefinedOrNull(obj) || DataUtility.isString(obj)) return obj; 51 | if (!DataUtility.isFunction(className)) return obj; 52 | 53 | ObjectUtility._replaceObjectReferences(obj, objToReplace, className); 54 | 55 | return obj; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/modules/utility/route.utility.ts: -------------------------------------------------------------------------------- 1 | // Refer: https://github.com/pillarjs/path-to-regexp 2 | // tslint:disable-next-line:no-require-imports 3 | import pathToRegexp = require('path-to-regexp'); 4 | import { Key } from 'path-to-regexp'; 5 | import { FunctionUtility } from './function.utility'; 6 | import { ObjectUtility } from './object.utility'; 7 | import { DataUtility } from './data.utility'; 8 | import { IKeyValuePair } from '../types'; 9 | 10 | export abstract class RouteUtility { 11 | 12 | // @returns {} when no segmented values are matched 13 | // @returns { id: 45 } for the matched segments values 14 | static getNamedSegmentKeyValues( 15 | // holds the original url of place holders 16 | // ex: user/:id 17 | originalUri: string, 18 | // holds the requested url which has values 19 | // user/45 20 | requestedUri: string): any { 21 | 22 | let keys: Key[] = []; 23 | let route = pathToRegexp(originalUri, keys); 24 | let values = route.exec(requestedUri); 25 | 26 | if (!DataUtility.isArray(values)) { 27 | return {}; 28 | } 29 | 30 | // According to "path-to-regexp" docs, 31 | // matched values start from index: 1 32 | let i = 1; 33 | let obj = {}; 34 | 35 | for (const key of keys) { 36 | obj[key.name] = values[i]; 37 | i++; 38 | } 39 | 40 | return obj; 41 | } 42 | 43 | // map the segmented values and query strings to action parameters. 44 | // if action parameter keys and variable segment / query string are matched, 45 | // it simply returns the array of values which are indexed to action parameter key 46 | static mapSegmentsAndQueryToActionArguments( 47 | originalUri: string, 48 | requestedUri: string, 49 | queryString: any, 50 | params: string[]): IKeyValuePair[] { 51 | 52 | let arr: string[] = []; 53 | 54 | if (params.length === 0) return []; 55 | 56 | let val = RouteUtility 57 | .getNamedSegmentKeyValues(originalUri, requestedUri); 58 | 59 | // If variable segment and query param have same keys 60 | // it overwrites with query string, since we are passing the query-keys 61 | // which are explicitly set using @QueryParam 62 | let values = Object.assign(val, queryString); 63 | 64 | // find the index of action parameter and 65 | // insert the value from url at matched index 66 | ObjectUtility.keys(values).forEach(key => { 67 | let index = params.findIndex(e => e === key); 68 | arr[index] = values[key]; 69 | }); 70 | 71 | return params.map((val: string, index: number) => { 72 | return { key: val, value: arr[index] }; 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test_export/index.ts: -------------------------------------------------------------------------------- 1 | // This file exports all the testable units to tests directory 2 | export * from '../api/attributes'; 3 | export * from '../api/dino'; 4 | export * from '../modules/builtin/exceptions/exceptions'; 5 | export * from '../modules/builtin/middlewares/dino.start.middleware'; 6 | export * from '../modules/builtin/middlewares/response.end.middleware'; 7 | export * from '../modules/builtin/middlewares/route.exception.middleware'; 8 | export * from '../modules/builtin/middlewares/route.notfound.middleware'; 9 | export * from '../modules/builtin/middlewares/task.context.middleware'; 10 | export * from '../modules/builtin/middlewares/http.response.exception.middleware'; 11 | export * from '../modules/builtin/middlewares/http.response.message.middleware'; 12 | export * from '../modules/builtin/middlewares/action.exception.middleware'; 13 | export * from '../modules/builtin/http_response'; 14 | export * from '../modules/builtin/parse_handlers'; 15 | export * from '../modules/builtin/providers/user.identity'; 16 | export * from '../modules/constants'; 17 | export * from '../modules/controller/api.controller'; 18 | export * from '../modules/controller/controller.action'; 19 | export * from '../modules/controller/error.controller'; 20 | export * from '../modules/core/app.container'; 21 | export * from '../modules/core/dicontainer'; 22 | export * from '../modules/core/dino.container'; 23 | export * from '../modules/core/dino.controller'; 24 | export * from '../modules/core/dino.error.controller'; 25 | export * from '../modules/entities/dino.response'; 26 | export * from '../modules/entities/dino.model'; 27 | export * from '../modules/entities/http.response.message'; 28 | export * from '../modules/filter/filter'; 29 | export * from '../modules/interfaces/idino'; 30 | export * from '../modules/lib/reflector'; 31 | export * from '../modules/metadata/attribute'; 32 | export * from '../modules/router/route.table'; 33 | export * from '../modules/router/dino.router'; 34 | export * from '../modules/sequence/deferrer'; 35 | export * from '../modules/types/attribute'; 36 | export * from '../modules/types/dino.types'; 37 | export * from '../modules/types/express'; 38 | export * from '../modules/types/types'; 39 | export * from '../modules/utility/data.utility'; 40 | export * from '../modules/utility/dino.parser'; 41 | export * from '../modules/utility/dino.utility'; 42 | export * from '../modules/utility/function.utility'; 43 | export * from '../modules/utility/http.utility'; 44 | export * from '../modules/utility/object.utility'; 45 | export * from '../modules/utility/route.utility'; 46 | -------------------------------------------------------------------------------- /tests/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../src/test_export/index'; 2 | // tslint:disable-next-line:no-implicit-dependencies 3 | export * from 'moq.ts'; 4 | -------------------------------------------------------------------------------- /tests/integration/controller.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | request, 3 | Controller, 4 | ApiController, 5 | HttpGet, 6 | Deferrer, 7 | ActionFilter, 8 | ActionFilterAsync, 9 | ResultFilter, 10 | ResultFilterAsync 11 | } from './index'; 12 | import { initializeTests } from './init'; 13 | 14 | const testResponse = 'hello'; 15 | const actionFilterOneAfterTxt = 'actionFilterOneAfter'; 16 | const actionFilterOneAsyncAfterTxt = 'actionFilterOneAsyncAfter'; 17 | const resultOneTxt = 'resultOne'; 18 | const resultOneAsyncTxt = 'resultOneAsync'; 19 | 20 | class ActionFilterOne extends ActionFilter { 21 | beforeExecution(request, response, next): void { 22 | next(); 23 | } 24 | afterExecution(request, response, next, result): void { 25 | response.locals.data.push(`${result}/${actionFilterOneAfterTxt}`); 26 | next(); 27 | } 28 | } 29 | 30 | class ActionFilterOneAsync extends ActionFilterAsync { 31 | async beforeExecution(request, response, next): Promise { 32 | next(); 33 | } 34 | async afterExecution(request, response, next, result): Promise { 35 | await Deferrer.run((res, rej) => { 36 | setTimeout(() => { 37 | response.locals.data.push(`${result}/${actionFilterOneAsyncAfterTxt}`); 38 | next(); 39 | }, 2); 40 | }); 41 | } 42 | } 43 | 44 | class ResultFilterOne extends ResultFilter { 45 | invoke(request, response, next, result): void { 46 | response.locals.data.push(`${result}/${resultOneTxt}`); 47 | next(); 48 | } 49 | } 50 | 51 | class ResultFilterOneAsync extends ResultFilterAsync { 52 | async invoke(request, response, next, result): Promise { 53 | await Deferrer.run((res, rej) => { 54 | setTimeout(() => { 55 | response.locals.data 56 | .push(`${result}/${resultOneAsyncTxt}`); 57 | response.status(200).json({ 58 | data: response.locals.data 59 | }); 60 | }, 2); 61 | }); 62 | } 63 | } 64 | 65 | @Controller('/test', { 66 | use: [(req, res, next) => { 67 | res.locals.data = []; 68 | next(); 69 | }], 70 | filters: [ActionFilterOne, ActionFilterOneAsync], 71 | result: [ResultFilterOne, ResultFilterOneAsync] 72 | }) 73 | class TestController extends ApiController { 74 | @HttpGet('/get') 75 | get(): string { 76 | return testResponse; 77 | } 78 | } 79 | 80 | describe('controller.middleware.e2e.spec', () => { 81 | it('controllerMiddlewares.verify_dinowares_flow', done => { 82 | const x = initializeTests(); 83 | const app = x.app; 84 | const dino = x.dino; 85 | const data = []; 86 | data.push(`${testResponse}/${actionFilterOneAfterTxt}`); 87 | data.push(`${testResponse}/${actionFilterOneAsyncAfterTxt}`); 88 | data.push(`${testResponse}/${resultOneTxt}`); 89 | data.push(`${testResponse}/${resultOneAsyncTxt}`); 90 | dino.registerController(TestController); 91 | dino.bind(); 92 | request(app) 93 | .get('/api/test/get') 94 | .expect('Content-Type', /json/) 95 | .expect(200, { data: data }, done); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /tests/integration/error.controller.spec.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-implicit-dependencies 2 | import { Response } from 'express'; 3 | import { 4 | request, 5 | Dino, 6 | Controller, 7 | ApiController, 8 | HttpGet, 9 | ErrorController 10 | } from './index'; 11 | import { initializeTests } from './init'; 12 | 13 | const nextError = 'NextError'; 14 | const testErr = 'TestError'; 15 | 16 | @Controller('/test') 17 | class TestController extends ApiController { 18 | 19 | @HttpGet('/get') 20 | query(): any { 21 | throw new Error(testErr); 22 | } 23 | 24 | @HttpGet('/next') 25 | nextErr(): any { 26 | throw new Error(nextError); 27 | } 28 | } 29 | 30 | class AppError extends ErrorController { 31 | internalServerError(): void { 32 | if (this.error.message === nextError) { 33 | this.next(this.error); 34 | } else { 35 | this.response.status(500).json({ 36 | data: this.error.message, 37 | id: this.request.query.id 38 | }); 39 | } 40 | } 41 | } 42 | 43 | describe('error.controller.e2e.spec', () => { 44 | const baseRoute = '/api/test'; 45 | 46 | function register(dino: Dino): void { 47 | dino.registerController(TestController); 48 | dino.registerApplicationError(AppError); 49 | dino.bind(); 50 | } 51 | 52 | // Verifies request, response and err property is mapped to dino 53 | it('/get_throws_error_returns_500_and_data', done => { 54 | const x = initializeTests(); 55 | const app = x.app; 56 | const dino = x.dino; 57 | register(dino); 58 | request(app) 59 | .get(`${baseRoute}/get?id=42`) 60 | .expect('Content-Type', /json/) 61 | .expect(500, { 62 | data: testErr, 63 | id: 42 64 | }, done); 65 | }); 66 | // Verifies next handler 67 | it('/next_throws_error_to_registered_express_err_when_app.use_after_dino.bind', done => { 68 | const x = initializeTests(); 69 | const app = x.app; 70 | const dino = x.dino; 71 | register(dino); 72 | app.use((err, req, res: Response, next) => { 73 | res.status(500).json({ 74 | data: 'From_Express', 75 | error: nextError 76 | }); 77 | }); 78 | request(app) 79 | .get(`${baseRoute}/next`) 80 | .expect('Content-Type', /json/) 81 | .expect(500, { 82 | data: 'From_Express', 83 | error: nextError 84 | }, done); 85 | }); 86 | 87 | // Following test is not essential because it basically tests expressjs code 88 | // However, do not remove it. This test keeps track of expressjs behavior. 89 | // Reason to comment: Error is written to console by expressjs, 90 | // we do not want to pollute the console by writing these errors by expressjs 91 | // it('/next_throws_error_to_built_express_err_when_app.use_after_dino.bind', done => { 92 | // const x = initializeTests(); 93 | // const app = x.app; 94 | // const dino = x.dino; 95 | // // This function is not invoked. 96 | // // We actually get response handled by builtin error middleware 97 | // // Refer: https://expressjs.com/en/guide/error-handling.html 98 | // app.use((err, req, res: Response, next) => { 99 | // res.status(500).json({ 100 | // data: 'From_Express', 101 | // error: nextError 102 | // }); 103 | // }); 104 | // register(dino); 105 | // request(app) 106 | // .get(`${baseRoute}/next`) 107 | // // Here we got response text/html 108 | // // sent by expressjs builtin middleware 109 | // // Note: error is written to console by expressjs 110 | // .expect('Content-Type', 'text/html; charset=utf-8') 111 | // .expect(500, done); 112 | // }); 113 | }); 114 | -------------------------------------------------------------------------------- /tests/integration/http.exception.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | request, 3 | Dino, 4 | Controller, 5 | ApiController, 6 | HttpGet, 7 | HttpResponseException, 8 | HttpStatusCode, 9 | BadRequestException, 10 | Async, 11 | ErrorMiddleware 12 | } from './index'; 13 | import { initializeTests } from './init'; 14 | 15 | class JsonError extends ErrorMiddleware { 16 | invoke(err, request, response, next): void { 17 | if (err instanceof HttpResponseException) { 18 | response.status(HttpStatusCode.oK) 19 | .json({ data: 'JsonErrorMiddleware' }); 20 | } else { 21 | next(err); 22 | } 23 | } 24 | } 25 | 26 | @Controller('/test') 27 | class TestController extends ApiController { 28 | @HttpGet('/throwSync') 29 | throwSync(): any { 30 | throw new HttpResponseException(HttpStatusCode.internalServerError, 31 | { data: 'InternalServerError' }); 32 | } 33 | 34 | @Async() 35 | @HttpGet('/throwAsync') 36 | async throwAsync(): Promise { 37 | throw new HttpResponseException(HttpStatusCode.unauthorized, 38 | { data: 'Unauthorized' }); 39 | } 40 | 41 | @HttpGet('/badRequest') 42 | requestEx(): Promise { 43 | throw new BadRequestException({ data: 'BadRequest' }); 44 | } 45 | } 46 | 47 | describe('http.exception.e2e.spec', () => { 48 | const baseRoute = '/api/test'; 49 | 50 | function register(dino: Dino): void { 51 | dino.registerController(TestController); 52 | dino.bind(); 53 | } 54 | 55 | it('throwSync.returns_HttpResponseException_with_internalServerError', done => { 56 | const x = initializeTests(); 57 | const app = x.app; 58 | const dino = x.dino; 59 | register(dino); 60 | request(app) 61 | .get(`${baseRoute}/throwSync`) 62 | .expect('Content-Type', /json/) 63 | .expect(500, { data: 'InternalServerError' }, done); 64 | }); 65 | it('throwASync.returns_HttpResponseException_with_unauthorized', done => { 66 | const x = initializeTests(); 67 | const app = x.app; 68 | const dino = x.dino; 69 | register(dino); 70 | request(app) 71 | .get(`${baseRoute}/throwAsync`) 72 | .expect('Content-Type', /json/) 73 | .expect(401, { data: 'Unauthorized' }, done); 74 | }); 75 | it('badRequest.returns_HttpResponseException_with_badRequest', done => { 76 | const x = initializeTests(); 77 | const app = x.app; 78 | const dino = x.dino; 79 | register(dino); 80 | request(app) 81 | .get(`${baseRoute}/badRequest`) 82 | .expect('Content-Type', /json/) 83 | .expect(400, { data: 'BadRequest' }, done); 84 | }); 85 | it('JsonError.custom_server_error_middleware_handles_HttpResponseException', done => { 86 | const x = initializeTests(); 87 | const app = x.app; 88 | const dino = x.dino; 89 | dino.registerController(TestController); 90 | dino.serverError(JsonError); 91 | dino.bind(); 92 | request(app) 93 | .get(`${baseRoute}/throwSync`) 94 | .expect('Content-Type', /json/) 95 | .expect(200, { data: 'JsonErrorMiddleware' }, done); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /tests/integration/http.response.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | request, 3 | Dino, 4 | Controller, 5 | ApiController, 6 | HttpGet, 7 | HttpResponseMessage, 8 | HttpStatusCode, 9 | RequestEndMiddleware, 10 | Ok 11 | } from './index'; 12 | import { initializeTests } from './init'; 13 | 14 | class JsonEnd extends RequestEndMiddleware { 15 | invoke(request, response, next, result): void { 16 | if (result instanceof HttpResponseMessage) { 17 | response.status(HttpStatusCode.oK) 18 | .json({ data: 'JsonEndMiddleware' }); 19 | } else { 20 | next(); 21 | } 22 | } 23 | } 24 | 25 | @Controller('/test') 26 | class TestController extends ApiController { 27 | @HttpGet('/result') 28 | result(): any { 29 | return new HttpResponseMessage(HttpStatusCode.oK, 30 | { data: 'JsonResult' }); 31 | } 32 | 33 | @HttpGet('/ok') 34 | ok(): any { 35 | return Ok({ data: 'OkResult' }); 36 | } 37 | } 38 | 39 | describe('http.response.e2e.spec', () => { 40 | const baseRoute = '/api/test'; 41 | 42 | function register(dino: Dino): void { 43 | dino.registerController(TestController); 44 | dino.bind(); 45 | } 46 | 47 | it('result.returns_HttpResponseMessage_with_ok', done => { 48 | const x = initializeTests(); 49 | const app = x.app; 50 | const dino = x.dino; 51 | register(dino); 52 | request(app) 53 | .get(`${baseRoute}/result`) 54 | .expect('Content-Type', /json/) 55 | .expect(200, { data: 'JsonResult' }, done); 56 | }); 57 | it('ok.returns_HttpResponseMessage_with_ok', done => { 58 | const x = initializeTests(); 59 | const app = x.app; 60 | const dino = x.dino; 61 | register(dino); 62 | request(app) 63 | .get(`${baseRoute}/ok`) 64 | .expect('Content-Type', /json/) 65 | .expect(200, { data: 'OkResult' }, done); 66 | }); 67 | it('JsonEnd.custom_request_end_middleware_handles_HttpResponseMessage', done => { 68 | const x = initializeTests(); 69 | const app = x.app; 70 | const dino = x.dino; 71 | dino.registerController(TestController); 72 | dino.requestEnd(JsonEnd); 73 | dino.bind(); 74 | request(app) 75 | .get(`${baseRoute}/result`) 76 | .expect('Content-Type', /json/) 77 | .expect(200, { data: 'JsonEndMiddleware' }, done); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /tests/integration/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index'; 2 | // tslint:disable-next-line:no-implicit-dependencies 3 | export * from 'moq.ts'; 4 | // tslint:disable-next-line:no-require-imports no-implicit-dependencies 5 | import express = require('express'); 6 | // tslint:disable-next-line:no-require-imports no-implicit-dependencies 7 | import bodyParser = require('body-parser'); 8 | // tslint:disable: no-implicit-dependencies no-require-imports 9 | import request = require('supertest'); 10 | import { Injectable, ReflectiveInjector } from 'injection-js'; 11 | 12 | export { bodyParser, request, express, Injectable, ReflectiveInjector }; 13 | -------------------------------------------------------------------------------- /tests/integration/init.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Dino, 3 | express 4 | } from './index'; 5 | // tslint:disable-next-line:no-require-imports no-implicit-dependencies 6 | import bodyParser = require('body-parser'); 7 | // tslint:disable-next-line:no-implicit-dependencies 8 | import { Express } from 'express'; 9 | 10 | export interface InitTest { 11 | app: Express, 12 | dino: Dino 13 | } 14 | 15 | export function initializeTests(): InitTest { 16 | 17 | const app = express(); 18 | const dino = new Dino(app, '/api'); 19 | app.use(bodyParser.json()); 20 | dino.useRouter(() => express.Router()); 21 | 22 | return { 23 | app: app, 24 | dino: dino 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /tests/integration/segments.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | request, 3 | Dino, 4 | Controller, 5 | ApiController, 6 | HttpGet, 7 | HttpPost 8 | } from './index'; 9 | import { initializeTests } from './init'; 10 | 11 | @Controller('/test') 12 | class TestController extends ApiController { 13 | 14 | @HttpGet('/:id') 15 | get(id: string): any { 16 | return { data: id }; 17 | } 18 | 19 | @HttpGet('/:user/images/:img') 20 | getImg(img: string, user: string): any { 21 | return { img: img, user: user }; 22 | } 23 | 24 | @HttpPost('/post') 25 | post(body: any): any { 26 | return body; 27 | } 28 | 29 | @HttpPost('/add/:id') 30 | add(body: any, id: string): any { 31 | return { body: body, id: id }; 32 | } 33 | 34 | @HttpGet('/optional/:id?') 35 | optional(id: string): any { 36 | // reason to return "undefined" 37 | // because json stringify will remove elements 38 | // which are undefined 39 | if (id === undefined) return { data: 'undefined' }; 40 | 41 | return { data: id }; 42 | } 43 | } 44 | 45 | describe('segments.e2e.spec', () => { 46 | const baseRoute = '/api/test'; 47 | 48 | function register(dino: Dino): void { 49 | dino.registerController(TestController); 50 | dino.bind(); 51 | } 52 | 53 | it('/:id.returns_data', done => { 54 | const x = initializeTests(); 55 | const app = x.app; 56 | const dino = x.dino; 57 | register(dino); 58 | request(app) 59 | .get(`${baseRoute}/22`) 60 | .expect('Content-Type', /json/) 61 | .expect(200, { data: '22' }, done); 62 | }); 63 | it('/:user/images/:img.returns_data', done => { 64 | const x = initializeTests(); 65 | const app = x.app; 66 | const dino = x.dino; 67 | register(dino); 68 | request(app) 69 | .get(`${baseRoute}/john/images/45`) 70 | .expect('Content-Type', /json/) 71 | .expect(200, { img: '45', user: 'john' }, done); 72 | }); 73 | it('/post.returns_data', done => { 74 | const x = initializeTests(); 75 | const app = x.app; 76 | const dino = x.dino; 77 | register(dino); 78 | request(app) 79 | .post(`${baseRoute}/post`) 80 | .send({ name: 'john' }) 81 | .expect('Content-Type', /json/) 82 | .expect(200, { name: 'john' }, done); 83 | }); 84 | it('/add/:id.returns_data', done => { 85 | const x = initializeTests(); 86 | const app = x.app; 87 | const dino = x.dino; 88 | register(dino); 89 | request(app) 90 | .post(`${baseRoute}/add/45`) 91 | .send({ name: 'john' }) 92 | .expect('Content-Type', /json/) 93 | .expect(200, { body: { name: 'john' }, id: '45' }, done); 94 | }); 95 | // Refer: For optional params 96 | // https://stackoverflow.com/questions/10020099/express-js-routing-optional-spat-param 97 | it('/optional/:id?.returns_data', done => { 98 | const x = initializeTests(); 99 | const app = x.app; 100 | const dino = x.dino; 101 | register(dino); 102 | request(app) 103 | .get(`${baseRoute}/optional/22`) 104 | .expect('Content-Type', /json/) 105 | .expect(200, { data: '22' }, done); 106 | }); 107 | it('/optional/:id?.returns_undefined', done => { 108 | const x = initializeTests(); 109 | const app = x.app; 110 | const dino = x.dino; 111 | register(dino); 112 | request(app) 113 | .get(`${baseRoute}/optional`) 114 | .expect('Content-Type', /json/) 115 | .expect(200, { data: 'undefined' }, done); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /tests/unit/fakes/fakes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Middleware, MiddlewareAsync, 3 | RequestEndMiddleware, 4 | RequestStartMiddleware, 5 | RequestStartMiddlewareAsync, 6 | RequestEndMiddlewareAsync, 7 | ActionFilter, 8 | ActionFilterAsync, 9 | ResultFilter, 10 | ResultFilterAsync, 11 | ErrorController, 12 | ErrorMiddleware, 13 | ErrorMiddlewareAsync, 14 | ExceptionFilter, 15 | ExceptionFilterAsync, 16 | ApiController, 17 | AppStartMiddleware 18 | } from '../index'; 19 | 20 | export class MiddlewareFake extends Middleware { 21 | invoke(request: any, response: any, next: any, data?: any): void { } 22 | } 23 | export class MiddlewareAsyncFake extends MiddlewareAsync { 24 | async invoke(request: any, response: any, next: any, data?: any): Promise { 25 | throw new Error('Method not implemented.'); 26 | } 27 | } 28 | export class AppStartMiddlewareFake extends AppStartMiddleware { 29 | invoke(): void { } 30 | } 31 | export class RequestStartMiddlewareFake extends RequestStartMiddleware { 32 | invoke(request: any, response: any, next: any): void { } 33 | } 34 | export class RequestStartMiddlewareAsyncFake extends RequestStartMiddlewareAsync { 35 | async invoke(request: any, response: any, next: any): Promise { 36 | throw new Error('Method not implemented.'); 37 | } 38 | } 39 | export class RequestEndMiddlewareFake extends RequestEndMiddleware { 40 | invoke(request: any, response: any, next: any, result: any): void { } 41 | } 42 | export class RequestEndMiddlewareAsyncFake extends RequestEndMiddlewareAsync { 43 | invoke(request: any, response: any, next: any, result: any): Promise { 44 | throw new Error('Method not implemented.'); 45 | } 46 | } 47 | export class ActionFilterFake extends ActionFilter { 48 | beforeExecution(request: any, response: any, next: any, data?: any): void { } 49 | afterExecution(request: any, 50 | response: any, next: any, result: any, data?: any): void { } 51 | } 52 | export class ActionFilterAsyncFake extends ActionFilterAsync { 53 | async beforeExecution(request: any, 54 | response: any, next: any, data?: any): Promise { 55 | throw new Error('Method not implemented.'); 56 | } 57 | async afterExecution(request: any, response: any, 58 | next: any, result: any, data?: any): Promise { 59 | throw new Error('Method not implemented.'); 60 | } 61 | } 62 | export class ResultFilterFake extends ResultFilter { 63 | invoke(request: any, response: any, next: any, result: any, data?: any): void { } 64 | } 65 | export class ResultFilterAsyncFake extends ResultFilterAsync { 66 | async invoke(request: any, response: any, 67 | next: any, result: any, data?: any): Promise { 68 | throw new Error('Method not implemented.'); 69 | } 70 | } 71 | export class ErrorControllerFake extends ErrorController { 72 | internalServerError(): void { } 73 | } 74 | export class ErrorMiddlewareFake extends ErrorMiddleware { 75 | invoke(err: Error, request: any, response: any, next: any): void { } 76 | } 77 | export class ErrorMiddlewareAsyncFake extends ErrorMiddlewareAsync { 78 | async invoke(err: Error, request: any, response: any, next: any): Promise { 79 | throw new Error('Method not implemented.'); 80 | } 81 | } 82 | export class ExceptionFilterFake extends ExceptionFilter { 83 | invoke(err: Error, request: any, response: any, next: any): void { } 84 | } 85 | export class ExceptionFilterAsyncFake extends ExceptionFilterAsync { 86 | invoke(err: Error, request: any, response: any, next: any): Promise { 87 | throw new Error('Method not implemented.'); 88 | } 89 | } 90 | export class ApiControllerFake extends ApiController { } 91 | -------------------------------------------------------------------------------- /tests/unit/fakes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fakes'; 2 | -------------------------------------------------------------------------------- /tests/unit/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index'; 2 | -------------------------------------------------------------------------------- /tests/unit/jasmine-helpers/helper.ts: -------------------------------------------------------------------------------- 1 | // must import this for jasmine unit tests to work 2 | import 'reflect-metadata'; -------------------------------------------------------------------------------- /tests/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | basePath: '../../', 4 | frameworks: ["jasmine", "karma-typescript"], 5 | plugins: [ 6 | "karma-jasmine", 7 | "karma-chrome-launcher", 8 | "karma-jasmine-html-reporter", 9 | "karma-typescript" 10 | ], 11 | mime: { 12 | 'text/x-typescript': ['ts'] 13 | }, 14 | files: [ 15 | "src/**/*.ts", 16 | "tests/index.ts", 17 | "tests/unit/**/*.ts" 18 | ], 19 | preprocessors: { 20 | "**/*.ts": "karma-typescript" 21 | }, 22 | reporters: ["kjhtml", "karma-typescript"], 23 | browsers: ["Chrome"], 24 | karmaTypescriptConfig: { 25 | tsconfig: "./tsconfig.spec.json", 26 | coverageOptions: { 27 | exclude: [/(index|fakes).ts/i, /\.(d|spec|test|fake)\.ts/i] 28 | } 29 | }, 30 | client: { 31 | clearContext: false, 32 | captureConsole: false 33 | }, 34 | port: 9876, 35 | colors: true, 36 | logLevel: config.LOG_WARN, 37 | autoWatch: true, 38 | singleRun: false, 39 | concurrency: Infinity 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/http_response/http.response.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Ok, 3 | NoContent, 4 | BadRequest, 5 | Unauthorized, 6 | NotFound, 7 | HttpStatusCode, 8 | HttpResponseMessage 9 | } from '../../../index'; 10 | 11 | describe('modules.builtin.http_response.spec', () => { 12 | it('Ok.verify_properties_without_content', () => { 13 | let o = Ok(); 14 | expect(o.statusCode).toBe(HttpStatusCode.oK); 15 | expect(o.content).toBe(undefined); 16 | expect(o instanceof HttpResponseMessage).toBe(true); 17 | }); 18 | it('Ok.verify_properties_with_content', () => { 19 | let o = Ok('test'); 20 | expect(o.statusCode).toBe(HttpStatusCode.oK); 21 | expect(o.content).toBe('test'); 22 | expect(o instanceof HttpResponseMessage).toBe(true); 23 | }); 24 | it('NoContent.verify_properties', () => { 25 | let o = NoContent(); 26 | expect(o.statusCode).toBe(HttpStatusCode.noContent); 27 | expect(o.content).toBe(undefined); 28 | expect(o instanceof HttpResponseMessage).toBe(true); 29 | }); 30 | it('BadRequest.verify_properties_without_content', () => { 31 | let o = BadRequest(); 32 | expect(o.statusCode).toBe(HttpStatusCode.badRequest); 33 | expect(o.content).toBe(undefined); 34 | expect(o instanceof HttpResponseMessage).toBe(true); 35 | }); 36 | it('BadRequest.verify_properties_with_content', () => { 37 | let o = BadRequest('test'); 38 | expect(o.statusCode).toBe(HttpStatusCode.badRequest); 39 | expect(o.content).toBe('test'); 40 | expect(o instanceof HttpResponseMessage).toBe(true); 41 | }); 42 | it('Unauthorized.verify_properties_without_content', () => { 43 | let o = Unauthorized(); 44 | expect(o.statusCode).toBe(HttpStatusCode.unauthorized); 45 | expect(o.content).toBe(undefined); 46 | expect(o instanceof HttpResponseMessage).toBe(true); 47 | }); 48 | it('Unauthorized.verify_properties_with_content', () => { 49 | let o = Unauthorized('test'); 50 | expect(o.statusCode).toBe(HttpStatusCode.unauthorized); 51 | expect(o.content).toBe('test'); 52 | expect(o instanceof HttpResponseMessage).toBe(true); 53 | }); 54 | it('NotFound.verify_properties_without_content', () => { 55 | let o = NotFound(); 56 | expect(o.statusCode).toBe(HttpStatusCode.notFound); 57 | expect(o.content).toBe(undefined); 58 | expect(o instanceof HttpResponseMessage).toBe(true); 59 | }); 60 | it('NotFound.verify_properties_with_content', () => { 61 | let o = NotFound('test'); 62 | expect(o.statusCode).toBe(HttpStatusCode.notFound); 63 | expect(o.content).toBe('test'); 64 | expect(o instanceof HttpResponseMessage).toBe(true); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/middlewares/action.exception.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpStatusCode, 3 | ActionParamExceptionMiddleware, 4 | ActionParamException, 5 | ActionParamExceptionCode 6 | } from '../../../index'; 7 | 8 | describe('modules.builtin.middlewares.parse.exception.middleware.spec', () => { 9 | it('invoke.sends_json_when_err_instanceof_ActionParamException', () => { 10 | let responseResult; 11 | let statusCode; 12 | let invoked = false; 13 | let next: any = () => invoked = true; 14 | let res: any = { 15 | status: code => { 16 | statusCode = code; 17 | 18 | return res; 19 | }, 20 | json: data => responseResult = data 21 | }; 22 | let err = new ActionParamException('a', 'b', 'c', 'd', ActionParamExceptionCode.boolean); 23 | new ActionParamExceptionMiddleware() 24 | .invoke(err, null, res, next); 25 | expect(responseResult).toEqual({ 26 | value: err.value, 27 | message: err.message 28 | }); 29 | expect(statusCode).toBe(HttpStatusCode.badRequest); 30 | expect(invoked).toBeFalsy(); 31 | }); 32 | it('invoke.next(err)_when_result_not_instanceof_ActionParamException', () => { 33 | let responseResult; 34 | let statusCode; 35 | let ex; 36 | let next: any = err => ex = err; 37 | let res: any = { 38 | status: code => { 39 | statusCode = code; 40 | 41 | return res; 42 | }, 43 | json: data => responseResult = data 44 | }; 45 | new ActionParamExceptionMiddleware() 46 | .invoke({ test: 'true' }, null, res, next); 47 | expect(responseResult).toBe(undefined); 48 | expect(statusCode).toBe(undefined); 49 | expect(ex).toEqual({ test: 'true' }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/middlewares/dino.start.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { DinoStartMiddleware } from '../../../index'; 2 | 3 | describe('modules.builtin.middlewares.dino.start.middleware.spec', () => { 4 | it('invoke.sets_dino_property_to_{}_and_invokes_next', () => { 5 | let res: any = { locals: {} }; 6 | let invoked = false; 7 | new DinoStartMiddleware() 8 | .invoke({}, res, () => invoked = true); 9 | expect(res.locals.dino).toEqual({}); 10 | expect(invoked).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/middlewares/http.exception.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpStatusCode, 3 | HttpResponseExceptionMiddleware, 4 | HttpResponseException 5 | } from '../../../index'; 6 | 7 | describe('modules.builtin.middlewares.http.exception.middleware.spec', () => { 8 | it('invoke.sends_json_when_err_instanceof_HttpResponseException', () => { 9 | let responseResult; 10 | let statusCode; 11 | let invoked = false; 12 | let next: any = () => invoked = true; 13 | let res: any = { 14 | status: code => { 15 | statusCode = code; 16 | 17 | return res; 18 | }, 19 | json: data => responseResult = data 20 | }; 21 | let err = new HttpResponseException(HttpStatusCode.badRequest, { data: 'test_data' }); 22 | new HttpResponseExceptionMiddleware() 23 | .invoke(err, null, res, next); 24 | expect(responseResult).toBe(err.content); 25 | expect(statusCode).toBe(HttpStatusCode.badRequest); 26 | expect(invoked).toBeFalsy(); 27 | }); 28 | it('invoke.next(err)_when_result_not_instanceof_HttpResponseException', () => { 29 | let responseResult; 30 | let statusCode; 31 | let ex; 32 | let next: any = err => ex = err; 33 | let res: any = { 34 | status: code => { 35 | statusCode = code; 36 | 37 | return res; 38 | }, 39 | json: data => responseResult = data 40 | }; 41 | new HttpResponseExceptionMiddleware() 42 | .invoke({ test: 'true' }, null, res, next); 43 | expect(responseResult).toBe(undefined); 44 | expect(statusCode).toBe(undefined); 45 | expect(ex).toEqual({ test: 'true' }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/middlewares/http.response.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpStatusCode, 3 | HttpResponseMessageMiddleware, 4 | HttpResponseMessage 5 | } from '../../../index'; 6 | 7 | describe('modules.builtin.middlewares.http.response.middleware.spec', () => { 8 | it('invoke.sends_json_when_result_instanceof_HttpResponseMessage', () => { 9 | let responseResult; 10 | let statusCode; 11 | let invoked = false; 12 | let next: any = () => invoked = true; 13 | let res: any = { 14 | status: code => { 15 | statusCode = code; 16 | 17 | return res; 18 | }, 19 | json: data => responseResult = data 20 | }; 21 | let result = new HttpResponseMessage(HttpStatusCode.oK, 22 | { data: 'test_data' }); 23 | new HttpResponseMessageMiddleware() 24 | .invoke({}, res, next, result); 25 | expect(responseResult).toBe(result.content); 26 | expect(statusCode).toBe(HttpStatusCode.oK); 27 | expect(invoked).toBeFalsy(); 28 | }); 29 | it('invoke.sends_json_when_HttpStatusCode_204_and_instanceof_HttpResponseMessage', () => { 30 | let responseResult; 31 | let statusCode; 32 | let invoked = false; 33 | let next: any = () => invoked = true; 34 | let res: any = { 35 | status: code => { 36 | statusCode = code; 37 | 38 | return res; 39 | }, 40 | json: data => responseResult = data 41 | }; 42 | let result = new HttpResponseMessage(HttpStatusCode.noContent); 43 | new HttpResponseMessageMiddleware() 44 | .invoke({}, res, next, result); 45 | expect(responseResult).toBe(undefined); 46 | expect(statusCode).toBe(HttpStatusCode.noContent); 47 | expect(invoked).toBeFalsy(); 48 | }); 49 | it('invoke.next()_when_result_not_instanceof_HttpResponseMessage', () => { 50 | let responseResult; 51 | let statusCode; 52 | let result = {}; 53 | let invoked = false; 54 | let next: any = () => invoked = true; 55 | let res: any = { 56 | status: code => { 57 | statusCode = code; 58 | 59 | return res; 60 | }, 61 | json: data => responseResult = data 62 | }; 63 | new HttpResponseMessageMiddleware() 64 | .invoke({}, res, next, result); 65 | expect(responseResult).toBe(undefined); 66 | expect(statusCode).toBe(undefined); 67 | expect(invoked).toBeTruthy(); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/middlewares/response.end.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { ResponseEndMiddleware, HttpStatusCode } from '../../../index'; 2 | 3 | describe('modules.builtin.middlewares.response.end.middleware.spec', () => { 4 | it('invoke.sends_json_response_when_result_exists', () => { 5 | let responseResult; 6 | let statusCode; 7 | let res: any = { 8 | status: code => { 9 | statusCode = code; 10 | 11 | return res; 12 | }, 13 | json: data => responseResult = data 14 | }; 15 | let result = { data: 'test_data' }; 16 | let invoked = false; 17 | new ResponseEndMiddleware() 18 | .invoke({}, res, null, result); 19 | expect(responseResult).toBe(result); 20 | expect(statusCode).toBe(HttpStatusCode.oK); 21 | }); 22 | it('invoke.sends_json_response_when_result_undefined', () => { 23 | let responseResult; 24 | let statusCode; 25 | let result = undefined; 26 | let res: any = { 27 | status: code => { 28 | statusCode = code; 29 | 30 | return res; 31 | }, 32 | end: data => responseResult = data 33 | }; 34 | let invoked = false; 35 | new ResponseEndMiddleware() 36 | .invoke({}, res, null, result); 37 | expect(responseResult).toBe(undefined); 38 | expect(statusCode).toBe(HttpStatusCode.noContent); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/middlewares/route.exception.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RouteExceptionMiddleware, 3 | RouteNotFoundException, 4 | HttpStatusCode 5 | } from '../../../index'; 6 | 7 | describe('modules.builtin.middlewares.route.exception.middleware.spec', () => { 8 | it('invoke.sends_response_when_RouteNotFoundException_occurred', () => { 9 | let responseResult; 10 | let statusCode; 11 | let res: any = { 12 | status: code => { 13 | statusCode = code; 14 | 15 | return res; 16 | }, 17 | json: data => responseResult = data 18 | }; 19 | let httpVerb = 'get'; 20 | let uri = '/api/test'; 21 | let err = new RouteNotFoundException(httpVerb, uri); 22 | let invoked = false; 23 | 24 | new RouteExceptionMiddleware() 25 | .invoke(err, {}, res, () => invoked = true); 26 | expect(responseResult).toBe(`Cannot ${httpVerb} ${uri}`); 27 | expect(statusCode).toBe(HttpStatusCode.notFound); 28 | expect(invoked).toBeFalsy(); 29 | }); 30 | it('invoke.invoked_next_err_handler_when_RouteNotFoundException_not_occurred', () => { 31 | let responseResult; 32 | let res: any = { 33 | json: data => responseResult = data 34 | }; 35 | let invoked = false; 36 | 37 | new RouteExceptionMiddleware() 38 | .invoke(new Error('TestError'), {}, {}, () => invoked = true); 39 | expect(invoked).toBeTruthy(); 40 | expect(responseResult).toBeUndefined(); 41 | }); 42 | it('invoke.invoked_next_err_handler_when_error_object_is_null', () => { 43 | let responseResult; 44 | let res: any = { 45 | json: data => responseResult = data 46 | }; 47 | let invoked = false; 48 | 49 | new RouteExceptionMiddleware() 50 | .invoke(null, {}, {}, () => invoked = true); 51 | expect(invoked).toBeTruthy(); 52 | expect(responseResult).toBeUndefined(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/middlewares/task.context.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { TaskContextMiddleware, UserIdentity } from '../../../index'; 2 | 3 | describe('modules.builtin.middlewares.task.context.middleware.spec', () => { 4 | it('invoke.context_must_be_instanceof_UserIdentity_and_invokes_next', () => { 5 | let res: any = { locals: { dino: {} } }; 6 | let invoked = false; 7 | new TaskContextMiddleware() 8 | .invoke({}, res, () => invoked = true); 9 | expect(res.locals.dino.context instanceof UserIdentity).toBeTruthy(); 10 | expect(invoked).toBeTruthy(); 11 | }); 12 | it('invoke.throws_TypeError_when_dino_is_not_set_on_res.local', () => { 13 | let res: any = { locals: {} }; 14 | expect(() => new TaskContextMiddleware() 15 | .invoke({}, res, () => null)).toThrowError(TypeError); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/unit/modules/builtin/providers/user.identity.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserIdentity } from '../../../index'; 2 | 3 | describe('modules.builtin.providers.user.identity.spec', () => { 4 | it('set.work_normally_without_error', () => { 5 | let userIdentity = new UserIdentity(); 6 | userIdentity.set('key1', 'test'); 7 | // Known: Not a valid way to unit test 8 | // not sure how to test since it is a void method 9 | // but we want a stable build 10 | expect(userIdentity.get('key1')).toBeDefined(); 11 | }); 12 | it('get.return_values_for_keys_which_are_set', () => { 13 | let userIdentity = new UserIdentity(); 14 | userIdentity.set('key1', 'test'); 15 | userIdentity.set('key2', undefined); 16 | userIdentity.set('key3', { a: 1 }); 17 | expect(userIdentity.get('key1')).toBe('test'); 18 | expect(userIdentity.get('key2')).toBe(undefined); 19 | expect(userIdentity.get('key3')).toEqual({ a: 1 }); 20 | }); 21 | it('get.return_undefined_when_key_not_set', () => { 22 | let userIdentity = new UserIdentity(); 23 | userIdentity.set('key1', 'test'); 24 | expect(userIdentity.get('key2')).toBeUndefined(); 25 | }); 26 | it('contains.return_false_when_key_not_exists', () => { 27 | let userIdentity = new UserIdentity(); 28 | expect(userIdentity.contains('key')).toBeFalsy(); 29 | }); 30 | it('contains.return_true_when_key_exists', () => { 31 | let userIdentity = new UserIdentity(); 32 | userIdentity.set('key1', 'test'); 33 | expect(userIdentity.contains('key1')).toBeTruthy(); 34 | }); 35 | it('contains.return_true_when_key_exists_and_value_is_undefined', () => { 36 | let userIdentity = new UserIdentity(); 37 | userIdentity.set('key1', undefined); 38 | expect(userIdentity.contains('key1')).toBeTruthy(); 39 | }); 40 | it('clear.clears_complete_objects_data', () => { 41 | let userIdentity = new UserIdentity(); 42 | userIdentity.set('key1', undefined); 43 | expect(userIdentity.contains('key1')).toBeTruthy(); 44 | userIdentity.clear(); 45 | expect(userIdentity.contains('key1')).toBeFalsy(); 46 | }); 47 | it('remove.deletes_key_when_key_exists_and_value_for_key_is_defined', () => { 48 | let userIdentity = new UserIdentity(); 49 | userIdentity.set('key1', 45); 50 | userIdentity.remove('key1'); 51 | expect(userIdentity.contains('key1')).toBeFalsy(); 52 | }); 53 | it('remove.deletes_key_when_key_exists_value_for_key_is_undefined', () => { 54 | let userIdentity = new UserIdentity(); 55 | userIdentity.set('key1', undefined); 56 | userIdentity.remove('key1'); 57 | expect(userIdentity.contains('key1')).toBeFalsy(); 58 | }); 59 | it('remove.deletes_key_when_key_not_exists', () => { 60 | let userIdentity = new UserIdentity(); 61 | userIdentity.remove('key1'); 62 | expect(userIdentity.contains('key1')).toBeFalsy(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/unit/modules/constants/constants.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import { RouteAttribute, Attribute, Constants } from '../../index'; 3 | 4 | describe('modules.constants.constants.spec', () => { 5 | it('keys_of_RouteAttribute_obj_should_match_values_of_Attribute_obj_keys_for_httpverbType', () => { 6 | // expected values are to be valid http-verbs/http-methods on express 7 | // This test also makes sure, value of Attribute.key is RouteAttribute key 8 | // Should be considered thoroughly before deleting this test case 9 | expect(RouteAttribute[Attribute.httpGet]).toBe('get'); 10 | expect(RouteAttribute[Attribute.httpPost]).toBe('post'); 11 | expect(RouteAttribute[Attribute.httpDelete]).toBe('delete'); 12 | expect(RouteAttribute[Attribute.httpPut]).toBe('put'); 13 | expect(RouteAttribute[Attribute.httpPatch]).toBe('patch'); 14 | expect(RouteAttribute[Attribute.httpHead]).toBe('head'); 15 | expect(RouteAttribute[Attribute.httpAll]).toBe('all'); 16 | }); 17 | it('Attribute.errorControllerDefaultMethod_should_be_internalServerError', () => { 18 | expect(Constants.errControllerDefaultMethod).toBe('internalServerError'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/unit/modules/controller/controller.action.spec.ts: -------------------------------------------------------------------------------- 1 | import { ControllerAction } from '../../index'; 2 | 3 | describe('modules.controller.controller.action.spec', () => { 4 | it('constructor.verify_properties', () => { 5 | let o = new ControllerAction({ 6 | sendsResponse: true, 7 | isAsync: false, 8 | route: '/api', 9 | httpVerb: '/test' 10 | }); 11 | expect(o.actionAttributes.sendsResponse).toBeTruthy(); 12 | expect(o.actionAttributes.isAsync).toBeFalsy(); 13 | expect(o.actionAttributes.route).toBe('/api'); 14 | expect(o.actionAttributes.httpVerb).toBe('/test'); 15 | }); 16 | it('static_create.verify_constructor_invoked', () => { 17 | let o = ControllerAction.create({ 18 | sendsResponse: true 19 | }); 20 | expect(o instanceof ControllerAction).toBeTruthy(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/unit/modules/core/dicontainer.spec.ts: -------------------------------------------------------------------------------- 1 | import { DIContainer } from '../../index'; 2 | 3 | describe('modules.core.dicontainer.spec', () => { 4 | it('resolve.invoke_new_operator_when_di_framework_is_not_configured', () => { 5 | let container = new DIContainer(undefined, undefined); 6 | // Should resolve String with new operator 7 | let obj = container.resolve(String); 8 | // its default value is '' 9 | expect(obj.toString()).toBe(''); 10 | }); 11 | it('static_create.invoke_constructor', () => { 12 | let container = DIContainer.create(undefined, undefined); 13 | expect(container instanceof DIContainer).toBeTruthy(); 14 | }); 15 | it('resolve.invoke_diResolve_when_di_framework_is_configured', () => { 16 | // DI container should be invoked and verify properties are properly injected 17 | let container = new DIContainer({ injector: true }, (injector, type) => { 18 | expect(injector).toEqual({ injector: true }); 19 | expect(type).toEqual(String); 20 | 21 | return 'resolved'; 22 | }); 23 | let obj = container.resolve(String); 24 | expect(obj).toBe('resolved'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/unit/modules/core/dino.error.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { DinoErrorController } from '../../index'; 2 | 3 | describe('modules.core.dino.error.controller.spec', () => { 4 | it('patch.verify_properties_are_mapped', () => { 5 | let obj: any = { dino: {} }; 6 | let err = new DinoErrorController(obj); 7 | let errHandler: any = {}; 8 | let reqHandler: any = {}; 9 | let resHandler: any = { locals: { dino: {} } }; 10 | let errObj; 11 | let nextHandler: any = e => errObj = e; 12 | err.patch(errHandler, reqHandler, resHandler, nextHandler); 13 | expect(obj.request).toBe(reqHandler); 14 | expect(obj.response).toBe(resHandler); 15 | expect(obj.next).toBe(nextHandler); 16 | expect(obj.error).toBe(errHandler); 17 | }); 18 | it('static_create.verify_constructor_invoked', () => { 19 | let obj: any = {}; 20 | let err = DinoErrorController.create(obj); 21 | expect(err instanceof DinoErrorController).toBeTruthy(); 22 | }); 23 | it('invoke.invoke_action_if_exists', () => { 24 | let invoked = false; 25 | let obj: any = { get: () => invoked = true }; 26 | let err = new DinoErrorController(obj); 27 | err.invoke('get'); 28 | expect(invoked).toBeTruthy(); 29 | }); 30 | it('invoke.throws_TypeError_if_action_not_exists', () => { 31 | let invoked = false; 32 | let obj: any = {}; 33 | let err = new DinoErrorController(obj); 34 | expect(() => err.invoke('get')).toThrowError(TypeError); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/unit/modules/router/router.table.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouteTable } from '../../index'; 2 | 3 | describe('modules.router.route.table.spec', () => { 4 | it('add.verify_/null_api/get_when_route_api/get_and_httpVerb_null', () => { 5 | let r = new RouteTable(); 6 | r.add('api/get', null); 7 | expect(r.getRoutes()[0]).toBe('/null_api/get'); 8 | }); 9 | it('static_create.invoke_constructor', () => { 10 | let r = RouteTable.create(); 11 | expect(r instanceof RouteTable).toBeTruthy(); 12 | }); 13 | it('add.verify_/undefined_api/get_when_route_api/get_httpVerb_undefined', () => { 14 | let r = new RouteTable(); 15 | r.add('api/get', undefined); 16 | expect(r.getRoutes()[0]).toBe('/undefined_api/get'); 17 | }); 18 | it('add.verify_/undefined_api/get_when_route_undefined_and_httpVerb_undefined', () => { 19 | let r = new RouteTable(); 20 | r.add(undefined, undefined); 21 | expect(r.getRoutes()[0]).toBe('/undefined_undefined'); 22 | }); 23 | it('add.verify_/undefined_api/get_when_route_null_and_httpVerb_null', () => { 24 | let r = new RouteTable(); 25 | r.add(null, null); 26 | expect(r.getRoutes()[0]).toBe('/null_null'); 27 | }); 28 | it('add.verify_/get_null_when_route_null_and_httpVerb_get', () => { 29 | let r = new RouteTable(); 30 | r.add(null, 'get'); 31 | expect(r.getRoutes()[0]).toBe('/get_null'); 32 | }); 33 | it('add.verify_/get_undefined_when_route_undefined_and_httpVerb_get', () => { 34 | let r = new RouteTable(); 35 | r.add(undefined, 'get'); 36 | expect(r.getRoutes()[0]).toBe('/get_undefined'); 37 | }); 38 | it('add.verify_lowercase_/get_api/getall_when_route_api/getAll_and_httpVerb_get', () => { 39 | let r = new RouteTable(); 40 | r.add('api/gEtAll', 'get'); 41 | expect(r.getRoutes()[0]).toBe('/get_api/getall'); 42 | }); 43 | it('add.verify_lowercase_/post_api/setall_when_route_api/getAll_and_httpVerb_post', () => { 44 | let r = new RouteTable(); 45 | r.add('api/sEtAll', 'post'); 46 | expect(r.getRoutes()[0]).toBe('/post_api/setall'); 47 | }); 48 | it('add.verify_lowercase_/:verb_api/setall_when_route_api/getAll_and_httpVerb_all', () => { 49 | let r = new RouteTable(); 50 | r.add('api/sEtAll', 'all'); 51 | expect(r.getRoutes()[0]).toBe('/:verb_api/setall'); 52 | }); 53 | it('getRoutes.return_[]_when_no_routes_added', () => { 54 | expect(new RouteTable().getRoutes()).toEqual([]); 55 | }); 56 | it('getRoutes.verify_when_array_of_routes_added', () => { 57 | let r = new RouteTable(); 58 | r.add('api/q', 'get'); 59 | r.add('api/r', 'all'); 60 | r.add('api/r/:id', 'all'); 61 | expect(r.getRoutes().includes('/get_api/q')).toBeTruthy(); 62 | expect(r.getRoutes().includes('/:verb_api/r')).toBeTruthy(); 63 | expect(r.getRoutes().includes('/:verb_api/r/:id')).toBeTruthy(); 64 | expect(r.getRoutes().includes('/:verb_apiI/r')).toBeFalsy(); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /tests/unit/modules/sequence/deferrer.spec.ts: -------------------------------------------------------------------------------- 1 | import { Deferrer } from '../../index'; 2 | 3 | describe('modules.sequence.deferrer.spec', () => { 4 | it('run.when_promise_resolved', async () => { 5 | let data = await Deferrer.run((res, rej) => { 6 | setTimeout(() => { 7 | res('success!'); 8 | }, 20); 9 | }); 10 | expect(data).toBe('success!'); 11 | }); 12 | it('run.when_promise_rejected', async () => { 13 | try { 14 | let data = await Deferrer.run((res, rej) => { 15 | setTimeout(() => { 16 | rej(new Error('test_error')); 17 | }, 20); 18 | }); 19 | // If code does not throw error, 20 | // following line will make sure to fail the test case 21 | expect(false).toBeTruthy(); 22 | } catch (err) { 23 | expect(err).toEqual(new Error('test_error')); 24 | } 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/unit/modules/utility/dino.parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { DinoParser, IMiddlewareProvider } from '../../index'; 2 | 3 | describe('modules.utility.dino.parser.spec', () => { 4 | it('parseMiddlewareProvider.returns_null_when_null', () => { 5 | let result = DinoParser.parseMiddlewareProvider(null); 6 | expect(result.data).toBeUndefined(); 7 | expect(result.useClass).toBeNull(); 8 | }); 9 | it('parseMiddlewareProvider.returns_undefined_when_undefined', () => { 10 | let result = DinoParser.parseMiddlewareProvider(undefined); 11 | expect(result.data).toBeUndefined(); 12 | expect(result.useClass).toBeUndefined(); 13 | }); 14 | it('parseMiddlewareProvider.return_Func_when_Func', () => { 15 | let result = DinoParser.parseMiddlewareProvider(String); 16 | expect(result.data).toBeUndefined(); 17 | expect(result.useClass).toBe(String); 18 | }); 19 | it('parseMiddlewareProvider.return_{}_when_{}', () => { 20 | let result = DinoParser.parseMiddlewareProvider({}); 21 | expect(result.data).toBeUndefined(); 22 | expect(result.useClass).toBeUndefined(); 23 | }); 24 | it('parseMiddlewareProvider.return_null_when_useClass_null', () => { 25 | let data: IMiddlewareProvider = { useClass: null }; 26 | let result = DinoParser.parseMiddlewareProvider(data); 27 | expect(result.data).toBeUndefined(); 28 | expect(result.useClass).toBeNull(); 29 | }); 30 | it('parseMiddlewareProvider.return_undefined_when_useClass_undefined', () => { 31 | let data: IMiddlewareProvider = { useClass: undefined }; 32 | let result = DinoParser.parseMiddlewareProvider(data); 33 | expect(result.data).toBeUndefined(); 34 | expect(result.useClass).toBeUndefined(); 35 | }); 36 | it('parseMiddlewareProvider.return_Func_when_useClass_Func_and_data_undefined', () => { 37 | let result = DinoParser.parseMiddlewareProvider({ useClass: String }); 38 | expect(result.data).toBeUndefined(); 39 | expect(result.useClass).toBe(String); 40 | }); 41 | it('parseMiddlewareProvider.return_String_when_has_useClass_and_data', () => { 42 | let result = DinoParser.parseMiddlewareProvider({ 43 | useClass: String, 44 | data: 'test' 45 | }); 46 | expect(result.data).toBe('test'); 47 | expect(result.useClass).toBe(String); 48 | }); 49 | it('parseMiddlewareProvider.return_null_when_has_data_but_useClass_null', () => { 50 | let data: IMiddlewareProvider = { useClass: null, data: 'test' }; 51 | let result = DinoParser.parseMiddlewareProvider(data); 52 | expect(result.data).toBeUndefined(); 53 | expect(result.useClass).toBeNull(); 54 | }); 55 | it('parseMiddlewareProvider.return_same_when_has_data_but_useClass_undefined', () => { 56 | let data: IMiddlewareProvider = { useClass: undefined, data: 'test' }; 57 | let result = DinoParser.parseMiddlewareProvider(data); 58 | expect(result.data).toBeUndefined(); 59 | expect(result.useClass).toBeUndefined(); 60 | }); 61 | it('parseMiddlewareProvider.return_same_when_has_data_useClass_NonFunc', () => { 62 | let data: IMiddlewareProvider = { useClass: Object(), data: 'test' }; 63 | let result = DinoParser.parseMiddlewareProvider(data); 64 | expect(result.data).toBeUndefined(); 65 | expect(result.useClass).toEqual(Object()); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /tests/unit/modules/utility/function.utility.spec.ts: -------------------------------------------------------------------------------- 1 | import { FunctionUtility } from '../../index'; 2 | 3 | describe('modules.utility.function.utility.spec', () => { 4 | it('getParamNames.return_[]_when_value_is_null', () => { 5 | expect(FunctionUtility.getParamNames(null)).toEqual([]); 6 | }); 7 | it('getParamNames.return_[]_when_value_is_undefined', () => { 8 | expect(FunctionUtility.getParamNames(undefined)).toEqual([]); 9 | }); 10 | it('getParamNames.return_[]_when_value_is_()', () => { 11 | let func = () => null; 12 | expect(FunctionUtility.getParamNames(func)).toEqual([]); 13 | }); 14 | it('getParamNames.return_[]_when_value_is_String', () => { 15 | expect(FunctionUtility.getParamNames(String)).toEqual([]); 16 | }); 17 | it('getParamNames.return_[a,b]_when_value_is_(a: number, b: boolean)', () => { 18 | let func = (a: number, b: boolean) => null; 19 | expect(FunctionUtility.getParamNames(func)).toEqual(['a', 'b']); 20 | }); 21 | it('getParamNames.return_[e]_when_value_is_(e:number)', () => { 22 | let func = (e: number) => null; 23 | expect(FunctionUtility.getParamNames(func)).toEqual(['e']); 24 | }); 25 | it('getParamNames.return_[e, d, c, a, b]_when_value_is_(e, d, c, a, b)', () => { 26 | let func = (e, d, c, a, b) => null; 27 | expect(FunctionUtility.getParamNames(func)).toEqual(['e', 'd', 'c', 'a', 'b']); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/unit/modules/utility/http.utility.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpUtility, 3 | RouteAttribute 4 | } from '../../index'; 5 | 6 | describe('modules.utility.http.utility.spec', () => { 7 | it('hasBody.returns_false_when_null', () => { 8 | let result = HttpUtility.hasBody(null); 9 | expect(result).toBeFalsy(); 10 | }); 11 | it('hasBody.returns_false_when_undefined', () => { 12 | let result = HttpUtility.hasBody(undefined); 13 | expect(result).toBeFalsy(); 14 | }); 15 | // Intentionally added literal strings for check 16 | // Just to make sure tests fail when RouteAttribute values are changed 17 | it('hasBody.returns_false_when_all', () => { 18 | let result = HttpUtility.hasBody(RouteAttribute.httpAll_ActionAttribute); 19 | expect(result).toBeFalsy(); 20 | }); 21 | it('hasBody.returns_false_when_get', () => { 22 | let result = HttpUtility.hasBody(RouteAttribute.httpGet_ActionAttribute); 23 | expect(result).toBeFalsy(); 24 | }); 25 | it('hasBody.returns_false_when_head', () => { 26 | let result = HttpUtility.hasBody(RouteAttribute.httpHead_ActionAttribute); 27 | expect(result).toBeFalsy(); 28 | }); 29 | it('hasBody.returns_false_when_delete', () => { 30 | let result = HttpUtility.hasBody(RouteAttribute.httpDelete_ActionAttribute); 31 | expect(result).toBeFalsy(); 32 | }); 33 | it('hasBody.returns_true_when_post', () => { 34 | let result = HttpUtility.hasBody(RouteAttribute.httpPost_ActionAttribute); 35 | expect(result).toBeTruthy(); 36 | }); 37 | it('hasBody.returns_true_when_put', () => { 38 | let result = HttpUtility.hasBody(RouteAttribute.httpPut_ActionAttribute); 39 | expect(result).toBeTruthy(); 40 | }); 41 | it('hasBody.returns_true_when_patch', () => { 42 | let result = HttpUtility.hasBody(RouteAttribute.httpPatch_ActionAttribute); 43 | expect(result).toBeTruthy(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "inlineSourceMap": true, 5 | "declaration": true, 6 | "outDir": "./dist" 7 | }, 8 | "exclude": [ 9 | "node_modules", 10 | "examples", 11 | "src/test_export", 12 | "tests/**/*.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ] 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "dist", 19 | "public", 20 | "npm" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "sourceMap": true 5 | }, 6 | "exclude": [ 7 | "node_modules", 8 | "examples" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /wiki/README.md: -------------------------------------------------------------------------------- 1 | # Wiki 2 | 3 | Welcome to the Dinoloop wiki! 4 | 5 | ## Installation and Setup 6 | * [Installation](https://github.com/ParallelTask/dinoloop/blob/master/wiki/installation_setup.md) 7 | * [Application Structure](https://github.com/ParallelTask/dinoloop/blob/master/wiki/application_structure.md) 8 | 9 | ## Documentation 10 | * [Dino properties](https://github.com/ParallelTask/dinoloop/blob/master/wiki/dino_properties.md) 11 | * [Attributes](https://github.com/ParallelTask/dinoloop/blob/master/wiki/attributes.md) 12 | * [Controllers](https://github.com/ParallelTask/dinoloop/blob/master/wiki/controllers.md) 13 | * [Application Middlewares](https://github.com/ParallelTask/dinoloop/blob/master/wiki/application_middlewares.md) 14 | * [Controller Middlewares](https://github.com/ParallelTask/dinoloop/blob/master/wiki/controller_middlewares.md) 15 | * [Exceptions](https://github.com/ParallelTask/dinoloop/blob/master/wiki/exceptions.md) 16 | * [Flow of Dinowares](https://github.com/ParallelTask/dinoloop/blob/master/wiki/flow_of_dinowares.md) 17 | * [ErrorMiddleware vs ErrorController](https://github.com/ParallelTask/dinoloop/blob/master/wiki/errom_vs_errorc.md) 18 | * [Dependency Injection with TDD](https://github.com/ParallelTask/dinoloop/blob/master/wiki/di_setup.md) 19 | * [Custom HttpResponseMessage and HttpResponseException](https://github.com/ParallelTask/dinoloop/blob/master/wiki/httpresponse_httpexception.md) 20 | * [Terminology](https://github.com/ParallelTask/dinoloop/blob/master/wiki/terminology.md) 21 | * [Update express.d.ts for Express type-hinting](https://github.com/ParallelTask/dinoloop/blob/master/wiki/type_hinting.md) 22 | * [FAQ](https://github.com/ParallelTask/dinoloop/blob/master/wiki/faq.md) 23 | -------------------------------------------------------------------------------- /wiki/application_structure.md: -------------------------------------------------------------------------------- 1 | # Application Structure 2 | Clone the [dinoloop-starter](https://github.com/ParallelTask/dinoloop-starter) and install the dependencies. Here are few steps: 3 | ``` 4 | git clone https://github.com/ParallelTask/dinoloop-starter.git 5 | cd dinoloop-starter 6 | npm install 7 | ``` 8 | 9 | ## Files 10 | 11 | Open project in your favorite editor (Recommended [VSCode](https://code.visualstudio.com/)). You will find bare minimum files in order to work with Dinoloop. 12 | 13 | ### app.ts 14 | 15 | ``` 16 | import express = require('express'); 17 | import bodyParser = require('body-parser'); 18 | import { Dino } from 'dinoloop'; 19 | import { HomeController } from './controllers/home.controller'; 20 | 21 | /**** basic express-setup ****/ 22 | const app = express(); 23 | app.use(bodyParser.json()); 24 | 25 | // Dino requires express instance 26 | // and base-uri to which dino will be mounted 27 | const dino = new Dino(app, '/api'); 28 | 29 | // Dino requires express router too 30 | dino.useRouter(() => express.Router()); 31 | 32 | // Register controller 33 | dino.registerController(HomeController); 34 | 35 | // Bind dino to express 36 | dino.bind(); 37 | 38 | // Start your express app 39 | app.listen(8088, () => console.log('Server started on port 8088')); 40 | ``` 41 | * Make sure to `bind()` dino before you start express. 42 | 43 | ### home.controller.ts 44 | 45 | ``` 46 | import { ApiController, Controller, HttpGet } from 'dinoloop'; 47 | 48 | // Set baseUri for all action methods 49 | @Controller('/home') 50 | export class HomeController extends ApiController { 51 | 52 | // Responds to HttpGet request 53 | @HttpGet('/get') 54 | get(): string { 55 | return 'Hello World!'; 56 | } 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /wiki/errom_vs_errorc.md: -------------------------------------------------------------------------------- 1 | # ErrorMiddleware vs ErrorController 2 | Dinoloop have support for multiple [ErrorMiddleware](https://github.com/ParallelTask/dinoloop/blob/master/wiki/application_middlewares.md#errormiddleware) and single [ErrorController](https://github.com/ParallelTask/dinoloop/blob/master/wiki/controllers.md#errorcontroller). This guide explains you the why. 3 | 4 | ### Why Multiple ErrorMiddlewares? 5 | Express.js has robust [error handling](https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling) using error middlewares. Dinoloop wants this great feature in its core. Awesome to have it :). 6 | 7 | We do have a problem somehow. How do you figure out *this middleware* is global error handler. You have to walkthrough all the middlewares logic or *filename*. 8 | 9 | Here comes the *ErrorController* to solve this problem. 10 | 11 | ### Why Single ErrorController? 12 | To me/others know exactly where to put application error logic. 13 | ``` 14 | import { ErroController } from 'dinoloop'; 15 | 16 | export class ApplicationErrorController extends ErrorController { 17 | internalServerError(): void { 18 | this.response.status(500).send('Internal server error occured'); 19 | } 20 | } 21 | ``` 22 | *ErrorController* is the last error handler in dinoloop, once the error goes out of its scope, it reaches to express. Now express can decide to crash container or handle it. 23 | -------------------------------------------------------------------------------- /wiki/exceptions.md: -------------------------------------------------------------------------------- 1 | # Exceptions 2 | 3 | ## RouteNotFoundException 4 | Dinoloop throws `RouteNotFoundException` when it receives a request that does not match a route. Here is how you can handle it 5 | ``` 6 | import { HttpStatusCode, ErrorMiddleware, RouteNotFoundException } from 'dinoloop'; 7 | import { Request, Response, NextFunction } from 'express'; 8 | 9 | export class RouteNotFoundErrorMiddleware extends ErrorMiddleware { 10 | 11 | invoke(err: Error, request: Request, response: Response, next: NextFunction): void { 12 | if (err instanceof RouteNotFoundException) { 13 | response.status(HttpStatusCode.NotFound).json('Link has been moved or broken'); 14 | } else next(err); 15 | } 16 | } 17 | 18 | // app.ts: Register with dino 19 | dino.serverError(RouteNotFoundErrorMiddleware); 20 | ``` 21 | * It is so easy to setup error-middlewares and add custom response. 22 | * It is recommended to register `RouteNotFoundErrorMiddleware` as first server-error middleware. 23 | ## CustomException 24 | Programmers from C#/Java have robust support for exception handling. Dinoloop suggests to create a `CustomException` and extend all your exceptions from this class. 25 | ``` 26 | export abstract class CustomException extends Error { 27 | innerException: Error; 28 | type: string; 29 | constructor(message: string, ex?: Error) { 30 | super(message); 31 | this.innerException = ex; 32 | Object.setPrototypeOf(this, new.target.prototype); 33 | } 34 | } 35 | 36 | // Extend CustomException 37 | export class InvalidOrderExceptions extends CustomException { 38 | constructor(msg: string, ex?: Erro) { 39 | super(msg, ex); 40 | } 41 | } 42 | 43 | let ex = new InvalidOrderException(); 44 | ex instanceof InvalidOrderException; true 45 | ``` 46 | ### Why CustomException? 47 | * One good reason is to hold inner exception where native javascript Error does not. 48 | -------------------------------------------------------------------------------- /wiki/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ### KeyPoints 4 | * When query-string and named-segments have same variables (or keys) then the named-segments values are injected into action args. 5 | ``` 6 | @HttpGet('/query/:name') 7 | query(name: string, search: string): string { 8 | return 'Hello World!'; 9 | } 10 | 11 | Following are the requests 12 | 13 | 1. server:8088/api/home/query/stack?search=queue 14 | name => stack, search => queue 15 | 16 | 2. server:8088/api/home/query/stack?name=queue 17 | name => stack, search => undefined (because name is taken from segment and not from query string) 18 | 19 | 3. server:8088/api/home/query/stack?search=queue&search=list 20 | name => stack, search => [queue, list] 21 | ``` 22 | * Result values (objects) are passed by reference, if one of your result filter mutates object the same object reflects in other filters. 23 | -------------------------------------------------------------------------------- /wiki/flow_of_dinowares.md: -------------------------------------------------------------------------------- 1 | # Flow of Dinowares 2 | This guide helps you to understand the flow of dinowares. 3 | 4 | When dinoloop receives a request and if it is a valid route to handle, this is what happens: 5 | 6 | ## Order of Dinowares 7 | * Array of RequestStartMiddlewares (Irrespective of controller). 8 | * Array of Expresswares specific to controller (registered via *use*). 9 | * Array of Middlewares specific to controller (registered via *middlewares*). 10 | * Array of ActionFilters specific to controller (registered via *filters*), `beforeExecution` methods are invoked. 11 | * Action method is invoked. 12 | * Array of ActionFilters specific to controller (registered via *filters*), `afterExecution` methods are invoked. 13 | * Array of ResultFilters specific to controller (registered via *result*). 14 | * Array of RequestEndMiddlewares (Irrespective of controller). 15 | 16 | `Note:` Execution of expresswares/dinowares is based on order of registration. 17 | 18 | ## Order of Dinowares in Base-Child Controller 19 | * Array of RequestStartMiddlewares (Irrespective of controller). 20 | * Array of Expresswares specific to base controller (registered via *use*). 21 | * Array of Expresswares specific to child controller (registered via *use*). 22 | * Array of Middlewares specific to base controller (registered via *middlewares*). 23 | * Array of Middlewares specific to child controller (registered via *middlewares*). 24 | * Array of ActionFilters specific to base controller (registered via *filters*), `beforeExecution` methods are invoked. 25 | * Array of ActionFilters specific to child controller (registered via *filters*), `beforeExecution` methods are invoked. 26 | * Action method is invoked. 27 | * Array of ActionFilters specific to child controller (registered via *filters*), `afterExecution` methods are invoked. 28 | * Array of ActionFilters specific to base controller (registered via *filters*), `afterExecution` methods are invoked. 29 | * Array of ResultFilters specific to child controller (registered via *result*). 30 | * Array of ResultFilters specific to base controller (registered via *result*). 31 | * Array of RequestEndMiddlewares (Irrespective of controller). 32 | 33 | ## Order of Dinowares When Error occured 34 | * Array of ExceptionFilters specific to controller (registered via *exceptions*). 35 | * Array of ErrorMiddlewares (Irrespective of controller). 36 | * Executes [ErroController](https://github.com/ParallelTask/dinoloop/blob/master/wiki/controllers.md#errorcontroller) implementation of `internalServerError()` method. 37 | 38 | ## Order of Dinowares When Error occured in Base-Child 39 | * Array of ExceptionFilters specific to child controller (registered via *exceptions*). 40 | * Array of ExceptionFilters specific to base controller (registered via *exceptions*). 41 | * Array of ErrorMiddlewares (Irrespective of controller). 42 | * Executes [ErroController](https://github.com/ParallelTask/dinoloop/blob/master/wiki/controllers.md#errorcontroller) implementation of `internalServerError()` method. 43 | -------------------------------------------------------------------------------- /wiki/installation_setup.md: -------------------------------------------------------------------------------- 1 | # Installation and Setup 2 | 3 | This guide gets you started with Dinoloop. By the end of this guide, you will have a working web server. 4 | 5 | ## Prerequisites 6 | Dinoloop is built on top of [ES6 Async](https://blog.risingstack.com/mastering-async-await-in-nodejs/), please make sure Node.js installed supports [Async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) and [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). Dinoloop uses [express.Router](https://expressjs.com/en/guide/routing.html) extensively for API routing, install Express.js that supports express.Router. 7 | 8 | * Node 8.10.x or higher 9 | * Express 4.x.x or higher 10 | 11 | ## Install 12 | 13 | ``` 14 | npm install dinoloop --save 15 | ``` 16 | 17 | ## Quickstart 18 | Setting up dinoloop project is quick and easy. Here are few steps: 19 | 20 | ``` 21 | git clone https://github.com/ParallelTask/dinoloop-starter.git 22 | cd dinoloop-starter 23 | npm install 24 | npm start 25 | ``` 26 | Navigate to [http:localhost:8088/api/home/get](http:localhost:8088/api/home/get) in browser 27 | 28 | ## Typescript Configuration 29 | Dinoloop requires the compilation options in your [tsconfig.json](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) file. 30 | 31 | ``` 32 | { 33 | "compilerOptions": { 34 | "target": "es5", 35 | "module": "commonjs", 36 | "moduleResolution": "node", 37 | "emitDecoratorMetadata": true, 38 | "experimentalDecorators": true, 39 | "lib": [ 40 | "es2017" 41 | ] 42 | } 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /wiki/terminology.md: -------------------------------------------------------------------------------- 1 | # Terminology 2 | 3 | ### ActionMethod 4 | Action methods are the exposed API methods defined on controller. 5 | Dinoloop treats a method as action method when it is decorated with http verbs like *@HttpGet, @HttpPost ...*. 6 | ### Dinoware 7 | Dinowares are middlewares that extend classes of Dinoloop. 8 | -------------------------------------------------------------------------------- /wiki/type_hinting.md: -------------------------------------------------------------------------------- 1 | # Type-hint for Express types 2 | Dinoloop philosophy is not to tie user to specific express version. With such philosophy there comes some caveats. The *Request, Response, NextFunction* are express types, to have type-hint on express objects in development, you are supposed to override `node_modules/dinoloop/modules/types/express.d.ts` contents after you install `@types/express`. 3 | 4 | First install express types 5 | ``` 6 | npm install @types/express --save-dev 7 | ``` 8 | Now paste following content inside `node_modules/dinoloop/modules/types/express.d.ts` 9 | ``` 10 | import { Request, Response, NextFunction, Express, Router } from '../../../@types/express'; 11 | export declare type Express = Express; 12 | export declare type Router = Router; 13 | export declare type Request = Request; 14 | export declare type Response = Response; 15 | export declare type NextFunction = NextFunction; 16 | ``` 17 | You will now be able to have type-hint on express objects. You will have to provide the path for express types where it is installed. 18 | --------------------------------------------------------------------------------