├── .cz-config.js ├── .eslintignore ├── .eslintrc.js ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── release.yaml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── prepare-commit-msg ├── .mocharc.json ├── .prettierignore ├── .prettierrc ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .yo-rc.json ├── CHANGELOG.md ├── DEVELOPING.md ├── LICENSE ├── README.md ├── architecture.jpg ├── commitlint.config.js ├── package-lock.json ├── package.json ├── src ├── __tests__ │ ├── acceptance │ │ └── README.md │ ├── integration │ │ └── README.md │ └── unit │ │ └── README.md ├── component.ts ├── index.ts ├── keys.ts ├── providers │ ├── README.md │ ├── datasource-identifier.provider.ts │ ├── datasource.provider.ts │ ├── dynamic-datasource-action.provider.ts │ └── index.ts └── types.ts └── tsconfig.json /.cz-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | types: [ 3 | {value: 'feat', name: 'feat: A new feature'}, 4 | {value: 'fix', name: 'fix: A bug fix'}, 5 | {value: 'docs', name: 'docs: Documentation only changes'}, 6 | { 7 | value: 'style', 8 | name: 'style: Changes that do not affect the meaning of the code\n (white-space, formatting, missing semi-colons, etc)', 9 | }, 10 | { 11 | value: 'refactor', 12 | name: 'refactor: A code change that neither fixes a bug nor adds a feature', 13 | }, 14 | { 15 | value: 'perf', 16 | name: 'perf: A code change that improves performance', 17 | }, 18 | {value: 'test', name: 'test: Adding missing tests'}, 19 | { 20 | value: 'chore', 21 | name: 'chore: Changes to the build process or auxiliary tools\n and libraries such as documentation generation', 22 | }, 23 | {value: 'revert', name: 'revert: Reverting a commit'}, 24 | {value: 'WIP', name: 'WIP: Work in progress'}, 25 | ], 26 | 27 | scopes: [ 28 | {name: 'chore'}, 29 | {name: 'ci-cd'}, 30 | {name: 'component'}, 31 | {name: 'provider'}, 32 | {name: 'core'}, 33 | {name: 'deps'}, 34 | ], 35 | 36 | appendBranchNameToCommitMessage: true, 37 | appendIssueFromBranchName: true, 38 | allowTicketNumber: false, 39 | isTicketNumberRequired: false, 40 | 41 | // override the messages, defaults are as follows 42 | messages: { 43 | type: "Select the type of change that you're committing:", 44 | scope: 'Denote the SCOPE of this change:', 45 | // used if allowCustomScopes is true 46 | customScope: 'Denote the SCOPE of this change:', 47 | subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n', 48 | body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n', 49 | breaking: 'List any BREAKING CHANGES (optional):\n', 50 | footer: 'List any ISSUES CLOSED by this change (optional). E.g.: GH-144:\n', 51 | confirmCommit: 'Are you sure you want to proceed with the commit above?', 52 | }, 53 | 54 | allowCustomScopes: false, 55 | allowBreakingChanges: ['feat', 'fix'], 56 | 57 | // limit subject length 58 | subjectLimit: 100, 59 | breaklineChar: '|', // It is supported for fields body and footer. 60 | footerPrefix: '', 61 | askForBreakingChangeFirst: true, // default is false 62 | }; 63 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .eslintrc.js 5 | .cz-config.js 6 | commitlint.config.js 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '@loopback/eslint-config', 3 | }; 4 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at support@sourcefuse.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # loopback4-notifications 2 | 3 | ## Contributing 4 | 5 | First off, thank you for considering contributing to the project. It's people like you that helps in keeping this extension useful. 6 | 7 | ### Where do I go from here ? 8 | 9 | If you've noticed a bug or have a question, [search the issue tracker](https://github.com/sourcefuse/loopback4-notifications/issues) to see if 10 | someone else in the community has already created a ticket. If not, go ahead and 11 | [make one](https://github.com/sourcefuse/loopback4-notifications/issues/new/choose)! 12 | 13 | ### Fork & create a branch 14 | 15 | If this is something you think you can fix, then [fork](https://help.github.com/articles/fork-a-repo) this repo and 16 | create a branch with a descriptive name. 17 | 18 | A good branch name would be (where issue #325 is the ticket you're working on): 19 | 20 | ```sh 21 | git checkout -b 325-add-new-feature 22 | ``` 23 | 24 | ### Make a Pull Request 25 | 26 | At this point, you should switch back to your master branch and make sure it's 27 | up to date with loopback4-notifications's master branch: 28 | 29 | ```sh 30 | git remote add upstream git@github.com:sourcefuse/loopback4-notifications.git 31 | git checkout master 32 | git pull upstream master 33 | ``` 34 | 35 | Then update your feature branch from your local copy of master, and push it! 36 | 37 | ```sh 38 | git checkout 325-add-new-feature 39 | git rebase master 40 | git push --set-upstream origin 325-add-new-feature 41 | ``` 42 | 43 | Finally, go to GitHub and [make a Pull Request](https://help.github.com/articles/creating-a-pull-request). 44 | 45 | ### Keeping your Pull Request updated 46 | 47 | If a maintainer asks you to "rebase" your PR, they're saying that a lot of code 48 | has changed, and that you need to update your branch so it's easier to merge. 49 | 50 | To learn more about rebasing in Git, there are a lot of [good][git rebasing] 51 | [resources][interactive rebase] but here's the suggested workflow: 52 | 53 | ```sh 54 | git checkout 325-add-new-feature 55 | git pull --rebase upstream master 56 | git push --force-with-lease 325-add-new-feature 57 | ``` 58 | 59 | [git rebasing]: http://git-scm.com/book/en/Git-Branching-Rebasing 60 | [interactive rebase]: https://help.github.com/articles/interactive-rebase 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] Intermediate change (work in progress) 15 | 16 | ## How Has This Been Tested? 17 | 18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 19 | 20 | - [ ] Test A 21 | - [ ] Test B 22 | 23 | ## Checklist: 24 | 25 | - [ ] Performed a self-review of my own code 26 | - [ ] npm test passes on your machine 27 | - [ ] New tests added or existing tests modified to cover all changes 28 | - [ ] Code conforms with the style guide 29 | - [ ] API Documentation in code was updated 30 | - [ ] Any dependent changes have been merged and published in downstream modules 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # This Manually Executable Workflow is for NPM Releases 2 | 3 | name: Release [Manual] 4 | on: workflow_dispatch 5 | permissions: 6 | contents: write 7 | jobs: 8 | Release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | # fetch-depth is necessary to get all tags 14 | # otherwise lerna can't detect the changes and will end up bumping the versions for all packages 15 | fetch-depth: 0 16 | token: ${{ secrets.RELEASE_COMMIT_GH_PAT }} 17 | - name: Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: '20.x' 21 | - name: Configure CI Git User 22 | run: | 23 | git config --global user.name $CONFIG_USERNAME 24 | git config --global user.email $CONFIG_EMAIL 25 | git remote set-url origin https://$GITHUB_ACTOR:$GITHUB_PAT@github.com/sourcefuse/loopback4-microservice-catalog 26 | env: 27 | GITHUB_PAT: ${{ secrets.RELEASE_COMMIT_GH_PAT }} 28 | CONFIG_USERNAME: ${{ vars.RELEASE_COMMIT_USERNAME }} 29 | CONFIG_EMAIL: ${{ vars.RELEASE_COMMIT_EMAIL }} 30 | - name: Authenticate with Registry 31 | run: | 32 | echo "@${NPM_USERNAME}:registry=https://registry.npmjs.org/" > .npmrc 33 | echo "registry=https://registry.npmjs.org/" >> .npmrc 34 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc 35 | npm whoami 36 | env: 37 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | NPM_USERNAME: ${{ vars.NPM_USERNAME }} 39 | 40 | - name: Install 📌 41 | run: | 42 | npm install 43 | - name: Test 🔧 44 | run: npm run test 45 | - name: Semantic Publish to NPM 🚀 46 | # "HUSKY=0" disables pre-commit-msg check (Needed in order to allow semantic-release perform the release commit) 47 | run: HUSKY=0 npx semantic-release 48 | env: 49 | GH_TOKEN: ${{ secrets.RELEASE_COMMIT_GH_PAT }} 50 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 51 | -------------------------------------------------------------------------------- /.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 (http://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 | # Transpiled JavaScript files from Typescript 61 | /dist 62 | 63 | # Cache used by TypeScript's incremental build 64 | *.tsbuildinfo 65 | 66 | # Webstorm files 67 | .idea/ 68 | 69 | # Mac file issue 70 | .DS_Store 71 | 72 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm test 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | exec < /dev/tty && npx cz --hook || true 5 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit": true, 3 | "recursive": true, 4 | "require": "source-map-support/register" 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | *.json 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "trailingComma": "all", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "node", 5 | "request": "launch", 6 | "name": "Launch Program", 7 | "skipFiles": [ 8 | "/**" 9 | ], 10 | "program": "${workspaceFolder}/dist/index.js", 11 | }, 12 | { 13 | "type": "node", 14 | "request": "launch", 15 | "name": "Run Mocha tests", 16 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 17 | "runtimeArgs": [ 18 | "-r", 19 | "${workspaceRoot}/node_modules/source-map-support/register" 20 | ], 21 | "cwd": "${workspaceRoot}", 22 | "autoAttachChildProcesses": true, 23 | "args": [ 24 | "--config", 25 | "${workspaceRoot}/.mocharc.json", 26 | "${workspaceRoot}/dist/__tests__/**/*.js", 27 | "-t", 28 | "0" 29 | ] 30 | }, 31 | { 32 | "type": "node", 33 | "request": "attach", 34 | "name": "Attach to Process", 35 | "port": 5858 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [80], 3 | "editor.tabCompletion": "on", 4 | "editor.tabSize": 2, 5 | "editor.trimAutoWhitespace": true, 6 | "editor.formatOnSave": true, 7 | "editor.codeActionsOnSave": { 8 | "source.organizeImports": true, 9 | "source.fixAll.eslint": true 10 | }, 11 | 12 | "files.exclude": { 13 | "**/.DS_Store": true, 14 | "**/.git": true, 15 | "**/.hg": true, 16 | "**/.svn": true, 17 | "**/CVS": true, 18 | "dist": true, 19 | }, 20 | "files.insertFinalNewline": true, 21 | "files.trimTrailingWhitespace": true, 22 | 23 | "typescript.tsdk": "./node_modules/typescript/lib", 24 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false, 25 | "typescript.preferences.quoteStyle": "single", 26 | "eslint.run": "onSave", 27 | "eslint.nodePath": "./node_modules", 28 | "eslint.validate": [ 29 | "javascript", 30 | "typescript" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.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": "Watch and Compile Project", 8 | "type": "shell", 9 | "command": "npm", 10 | "args": ["--silent", "run", "build:watch"], 11 | "group": { 12 | "kind": "build", 13 | "isDefault": true 14 | }, 15 | "problemMatcher": "$tsc-watch" 16 | }, 17 | { 18 | "label": "Build, Test and Lint", 19 | "type": "shell", 20 | "command": "npm", 21 | "args": ["--silent", "run", "test:dev"], 22 | "group": { 23 | "kind": "test", 24 | "isDefault": true 25 | }, 26 | "problemMatcher": ["$tsc", "$eslint-compact", "$eslint-stylish"] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@loopback/cli": { 3 | "packageManager": "npm", 4 | "version": "2.21.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/compare/v2.0.0...v2.0.1) (2024-12-17) 2 | 3 | # [2.0.0](https://github.com/sourcefuse/loopBack4-dynamic-datasource/compare/v1.0.0...v2.0.0) (2024-06-05) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * **chore:** bump version ([295fa06](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/295fa0619663c28ac5fd0a54b75c0ae556d3e74e)) 9 | * **chore:** bump version ([a67b5f1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a67b5f1f658b49788d27c9bb0bbfcfbc047a5e8f)) 10 | * **chore:** update package.json git repo ([7ea4f8f](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/7ea4f8ff103cfa464e720d95717bd4182bbcd53c)) 11 | * **ci-cd:** update release.yaml ([b376d67](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/b376d674fd2c6552e559ddb8d3b065c4e0a5adfb)) 12 | * **core:** release automation ([05e14a1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/05e14a1766ae16a88ca3143de4f73d8304cc9da7)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 13 | * **provider:** move deps to dev deps ([a4709f7](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a4709f776b0b73a9baf464cfc5898d435e2ffd16)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 14 | 15 | 16 | ### chore 17 | 18 | * **ci-cd:** loopback version update ([06de1fc](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/06de1fcc44328971b337ecb20a9cb2a2de06910c)), closes [#10](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/10) 19 | 20 | 21 | ### Features 22 | 23 | * **chore:** update lb4 dependencies ([acb34b7](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/acb34b75eeabf74b1310bc8354da17acdf424e58)), closes [#10](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/10) 24 | * **core:** bump version for release ([40c7c51](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/40c7c51ad764efedc952024f59e2bf9bb31c8b65)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 25 | * **loopback4-dynamic-datasource:** package update ([d90ff03](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/d90ff031e5421295e3c34332653a6da65432295a)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 26 | * **loopback4-dynamic-datasource:** version update ([df09f60](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/df09f60bc962afa01a897e482ad145c67dc85b36)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 27 | 28 | 29 | ### BREAKING CHANGES 30 | 31 | * **ci-cd:** loopback version update 32 | * **chore:** YES 33 | 34 | # [1.1.0](https://github.com/sourcefuse/loopBack4-dynamic-datasource/compare/v1.0.0...v1.1.0) (2024-03-01) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * **chore:** bump version ([295fa06](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/295fa0619663c28ac5fd0a54b75c0ae556d3e74e)) 40 | * **chore:** bump version ([a67b5f1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a67b5f1f658b49788d27c9bb0bbfcfbc047a5e8f)) 41 | * **chore:** update package.json git repo ([7ea4f8f](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/7ea4f8ff103cfa464e720d95717bd4182bbcd53c)) 42 | * **ci-cd:** update release.yaml ([b376d67](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/b376d674fd2c6552e559ddb8d3b065c4e0a5adfb)) 43 | * **core:** release automation ([05e14a1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/05e14a1766ae16a88ca3143de4f73d8304cc9da7)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 44 | * **provider:** move deps to dev deps ([a4709f7](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a4709f776b0b73a9baf464cfc5898d435e2ffd16)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 45 | 46 | 47 | ### Features 48 | 49 | * **core:** bump version for release ([40c7c51](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/40c7c51ad764efedc952024f59e2bf9bb31c8b65)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 50 | * **loopback4-dynamic-datasource:** package update ([d90ff03](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/d90ff031e5421295e3c34332653a6da65432295a)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 51 | * **loopback4-dynamic-datasource:** version update ([df09f60](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/df09f60bc962afa01a897e482ad145c67dc85b36)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 52 | 53 | # [1.1.0](https://github.com/sourcefuse/loopBack4-dynamic-datasource/compare/v1.0.0...v1.1.0) (2024-03-01) 54 | 55 | 56 | ### Bug Fixes 57 | 58 | * **chore:** bump version ([295fa06](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/295fa0619663c28ac5fd0a54b75c0ae556d3e74e)) 59 | * **chore:** bump version ([a67b5f1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a67b5f1f658b49788d27c9bb0bbfcfbc047a5e8f)) 60 | * **chore:** update package.json git repo ([7ea4f8f](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/7ea4f8ff103cfa464e720d95717bd4182bbcd53c)) 61 | * **ci-cd:** update release.yaml ([b376d67](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/b376d674fd2c6552e559ddb8d3b065c4e0a5adfb)) 62 | * **provider:** move deps to dev deps ([a4709f7](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a4709f776b0b73a9baf464cfc5898d435e2ffd16)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 63 | 64 | 65 | ### Features 66 | 67 | * **core:** bump version for release ([40c7c51](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/40c7c51ad764efedc952024f59e2bf9bb31c8b65)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 68 | * **loopback4-dynamic-datasource:** package update ([d90ff03](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/d90ff031e5421295e3c34332653a6da65432295a)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 69 | * **loopback4-dynamic-datasource:** version update ([df09f60](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/df09f60bc962afa01a897e482ad145c67dc85b36)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 70 | 71 | # [1.1.0](https://github.com/sourcefuse/loopBack4-dynamic-datasource/compare/v1.0.0...v1.1.0) (2024-03-01) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * **chore:** bump version ([295fa06](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/295fa0619663c28ac5fd0a54b75c0ae556d3e74e)) 77 | * **chore:** bump version ([a67b5f1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a67b5f1f658b49788d27c9bb0bbfcfbc047a5e8f)) 78 | * **chore:** update package.json git repo ([7ea4f8f](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/7ea4f8ff103cfa464e720d95717bd4182bbcd53c)) 79 | * **ci-cd:** update release.yaml ([b376d67](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/b376d674fd2c6552e559ddb8d3b065c4e0a5adfb)) 80 | 81 | 82 | ### Features 83 | 84 | * **core:** bump version for release ([40c7c51](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/40c7c51ad764efedc952024f59e2bf9bb31c8b65)), closes [#5](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/5) 85 | * **loopback4-dynamic-datasource:** package update ([d90ff03](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/d90ff031e5421295e3c34332653a6da65432295a)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 86 | * **loopback4-dynamic-datasource:** version update ([df09f60](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/df09f60bc962afa01a897e482ad145c67dc85b36)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 87 | 88 | # [1.1.0](https://github.com/sourcefuse/loopBack4-dynamic-datasource/compare/v1.0.0...v1.1.0) (2024-03-01) 89 | 90 | 91 | ### Bug Fixes 92 | 93 | * **chore:** bump version ([295fa06](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/295fa0619663c28ac5fd0a54b75c0ae556d3e74e)) 94 | * **chore:** bump version ([a67b5f1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a67b5f1f658b49788d27c9bb0bbfcfbc047a5e8f)) 95 | * **chore:** update package.json git repo ([7ea4f8f](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/7ea4f8ff103cfa464e720d95717bd4182bbcd53c)) 96 | * **ci-cd:** update release.yaml ([b376d67](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/b376d674fd2c6552e559ddb8d3b065c4e0a5adfb)) 97 | 98 | 99 | ### Features 100 | 101 | * **loopback4-dynamic-datasource:** package update ([d90ff03](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/d90ff031e5421295e3c34332653a6da65432295a)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 102 | * **loopback4-dynamic-datasource:** version update ([df09f60](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/df09f60bc962afa01a897e482ad145c67dc85b36)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 103 | 104 | # [1.1.0](https://github.com/sourcefuse/loopBack4-dynamic-datasource/compare/v1.0.0...v1.1.0) (2024-03-01) 105 | 106 | 107 | ### Bug Fixes 108 | 109 | * **chore:** bump version ([a67b5f1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/a67b5f1f658b49788d27c9bb0bbfcfbc047a5e8f)) 110 | * **chore:** update package.json git repo ([7ea4f8f](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/7ea4f8ff103cfa464e720d95717bd4182bbcd53c)) 111 | * **ci-cd:** update release.yaml ([b376d67](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/b376d674fd2c6552e559ddb8d3b065c4e0a5adfb)) 112 | 113 | 114 | ### Features 115 | 116 | * **loopback4-dynamic-datasource:** package update ([d90ff03](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/d90ff031e5421295e3c34332653a6da65432295a)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 117 | * **loopback4-dynamic-datasource:** version update ([df09f60](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/df09f60bc962afa01a897e482ad145c67dc85b36)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 118 | 119 | # [1.1.0](https://github.com/sourcefuse/loopBack4-dynamic-datasource/compare/v1.0.0...v1.1.0) (2024-03-01) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * **chore:** update package.json git repo ([7ea4f8f](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/7ea4f8ff103cfa464e720d95717bd4182bbcd53c)) 125 | * **ci-cd:** update release.yaml ([b376d67](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/b376d674fd2c6552e559ddb8d3b065c4e0a5adfb)) 126 | 127 | 128 | ### Features 129 | 130 | * **loopback4-dynamic-datasource:** package update ([d90ff03](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/d90ff031e5421295e3c34332653a6da65432295a)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 131 | * **loopback4-dynamic-datasource:** version update ([df09f60](https://github.com/sourcefuse/loopBack4-dynamic-datasource/commit/df09f60bc962afa01a897e482ad145c67dc85b36)), closes [#1](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/1) 132 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developer's Guide 2 | 3 | We use Visual Studio Code for developing LoopBack and recommend the same to our 4 | users. 5 | 6 | ## Setup Commit Hooks 7 | 8 | Run the following script to prepare husky after the first time install - 9 | 10 | `npm run prepare` 11 | 12 | ## VSCode setup 13 | 14 | Install the following extensions: 15 | 16 | - [eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 17 | - [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 18 | 19 | ## Development workflow 20 | 21 | ### Visual Studio Code 22 | 23 | 1. Start the build task (Cmd+Shift+B) to run TypeScript compiler in the 24 | background, watching and recompiling files as you change them. Compilation 25 | errors will be shown in the VSCode's "PROBLEMS" window. 26 | 27 | 2. Execute "Run Rest Task" from the Command Palette (Cmd+Shift+P) to re-run the 28 | test suite and lint the code for both programming and style errors. Linting 29 | errors will be shown in VSCode's "PROBLEMS" window. Failed tests are printed 30 | to terminal output only. 31 | 32 | ### Other editors/IDEs 33 | 34 | 1. Open a new terminal window/tab and start the continuous build process via 35 | `npm run build:watch`. It will run TypeScript compiler in watch mode, 36 | recompiling files as you change them. Any compilation errors will be printed 37 | to the terminal. 38 | 39 | 2. In your main terminal window/tab, run `npm run test:dev` to re-run the test 40 | suite and lint the code for both programming and style errors. You should run 41 | this command manually whenever you have new changes to test. Test failures 42 | and linter errors will be printed to the terminal. 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2020] [SourceFuse] 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loopback4-dynamic-datasource 2 | 3 | [![LoopBack](https://github.com/strongloop/loopback-next/raw/master/docs/site/imgs/branding/Powered-by-LoopBack-Badge-(blue)-@2x.png)](http://loopback.io/) 4 | 5 | This is a loopback 4 extension to connect dynamic datasources runtime. In todays world there can be a use case of multi tenant system in which we need the physical 6 | seperation of the databses so in loopback we need to connect to those datasources runtime and maintain and reuse those connection. 7 | 8 | ![Image of Architecture](https://github.com/sourcefuse/loopback4-dynamic-datasource/blob/master/architecture.jpg) 9 | ## Install 10 | 11 | ```sh 12 | npm install loopback4-dynamic-datasource 13 | ``` 14 | 15 | ## Usage 16 | 17 | In order to use this component into your LoopBack application, please follow below steps. 18 | 19 | - Add component to application. 20 | 21 | ```ts 22 | this.component(Loopback4DynamicDatasourceComponent); 23 | ``` 24 | 25 | - Now add action provider to the action based sequence (This step is not required in case of middleware based sequence) 26 | 27 | ```ts 28 | export class MySequence implements SequenceHandler { 29 | 30 | constructor( 31 | ... 32 | @inject(DynamicDatasourceBindings.DYNAMIC_DATASOURCE_ACTION) 33 | private readonly setupDatasource: SetupDatasourceFn, 34 | ... 35 | ) {} 36 | 37 | async handle(context: RequestContext) { 38 | const requestTime = Date.now(); 39 | try { 40 | ... 41 | await this.setupDatasource(context); 42 | ... 43 | } catch (err) { 44 | this.reject(context, err); 45 | } 46 | } 47 | 48 | } 49 | ``` 50 | 51 | - Now write a datasource identifier provider that is used to identify runtime which source we need to connect. 52 | In below example getting the identifier from the tenantId coming in query of the request. 53 | ```ts 54 | import {DatasourceIdentifierFn} from 'loopback4-dynamic-datasouce'; 55 | 56 | export class CustomDatasourceIdentifierProvider implements Provider { 57 | constructor() { 58 | } 59 | 60 | value(): DatasourceIdentifierFn { 61 | return async (requestCtx) => { 62 | const tenantId = requestCtx.request.query['tenantId'] as string; 63 | return tenantId == null ? null : {id: tenantId}; 64 | }; 65 | } 66 | } 67 | ``` 68 | Now bind that provider in application.ts 69 | ```ts 70 | this.bind(DynamicDatasourceBindings.DATASOURCE_IDENTIFIER_PROVIDER).toProvider(CustomDatasourceIdentifierProvider); 71 | ``` 72 | - Now write a datasource provider to get datasources runtime. these datasource will be created runtime on require basis 73 | ```ts 74 | export class CustomDatasourceProvider implements Provider { 75 | constructor( 76 | @repository(TenantRepository) 77 | private tenantRepo: TenantRepository, 78 | ) { 79 | } 80 | 81 | value(): DatasourceProviderFn { 82 | return async (datasourceIdentifier) => { 83 | return { 84 | pgdb: async () => { 85 | const tenantData = await this.tenantRepo.findById(datasourceIdentifier.id); 86 | return new juggler.DataSource({ 87 | ...tenantData.dbConfig, 88 | }); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | ``` 95 | Now bind that provider in application.ts 96 | ```ts 97 | this.bind(DynamicDatasourceBindings.DATASOURCE_PROVIDER).toProvider(CustomDatasourceProvider); 98 | ``` 99 | Note:- connector of following datasource should be present in package.json like in this example we are using **loopback-connector-postgresql** 100 | 101 | Now return of this provider is an object where you can give as many keys you want but that should return juggler.Datasource 102 | This is used as the intention of connecting multiple datasource for tenant. 103 | `pgdb` this key is custom and it can be used as per your choice but your repository must use specified key in injection 104 | 105 | ```ts 106 | export class UserRepository extends DefaultCrudRepository { 109 | constructor( 110 | @inject('datasources.pgdb') dataSource: JugglerDataSource, 111 | ) { 112 | super(User, dataSource); 113 | } 114 | } 115 | ``` 116 | 117 | That's all. 118 | 119 | ## Feedback 120 | 121 | If you've noticed a bug or have a question or have a feature request, [search the issue tracker](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues) to see if someone else in the community has already created a ticket. 122 | If not, go ahead and [make one](https://github.com/sourcefuse/loopBack4-dynamic-datasource/issues/new/choose)! 123 | All feature requests are welcome. Implementation time may vary. Feel free to contribute the same, if you can. 124 | If you think this extension is useful, please [star](https://help.github.com/en/articles/about-stars) it. Appreciation really helps in keeping this project alive. 125 | 126 | ## Contributing 127 | 128 | Please read [CONTRIBUTING.md](https://github.com/sourcefuse/loopBack4-dynamic-datasource/blob/master/.github/CONTRIBUTING.md) for details on the process for submitting pull requests to us. 129 | 130 | ## Code of conduct 131 | 132 | Code of conduct guidelines [here](https://github.com/sourcefuse/loopBack4-dynamic-datasource/blob/master/.github/CODE_OF_CONDUCT.md). 133 | 134 | ## License 135 | 136 | [MIT](https://github.com/sourcefuse/loopBack4-dynamic-datasource/blob/master/LICENSE) 137 | -------------------------------------------------------------------------------- /architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcefuse/loopback4-dynamic-datasource/faead38d460c403f803e8512aafc42153854d7e5/architecture.jpg -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'header-max-length': [2, 'always', 100], 5 | 'body-leading-blank': [2, 'always'], 6 | 'footer-leading-blank': [0, 'always'], 7 | 'references-empty': [2, 'never'], 8 | }, 9 | parserPreset: { 10 | parserOpts: { 11 | issuePrefixes: ['GH-'], 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback4-dynamic-datasource", 3 | "version": "2.0.1", 4 | "description": "An extension to connect to dynamic datasource in a loopback 4 application.", 5 | "keywords": [ 6 | "loopback-extension", 7 | "loopback" 8 | ], 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "engines": { 12 | "node": "18 || 20" 13 | }, 14 | "scripts": { 15 | "build": "lb-tsc", 16 | "build:watch": "lb-tsc --watch", 17 | "lint": "npm run eslint && npm run prettier:check", 18 | "lint:fix": "npm run eslint:fix && npm run prettier:fix", 19 | "prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"", 20 | "prettier:check": "npm run prettier:cli -- -l", 21 | "prettier:fix": "npm run prettier:cli -- --write", 22 | "eslint": "lb-eslint --report-unused-disable-directives .", 23 | "eslint:fix": "npm run eslint -- --fix", 24 | "pretest": "npm run rebuild", 25 | "test": "echo \"No test specified\"", 26 | "posttest": "npm run lint", 27 | "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest", 28 | "clean": "lb-clean dist *.tsbuildinfo .eslintcache", 29 | "rebuild": "npm run clean && npm run build", 30 | "prepublishOnly": "npm run test && npm run lint", 31 | "prepare": "husky install" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/sourcefuse/loopBack4-dynamic-datasource" 36 | }, 37 | "author": "", 38 | "license": "MIT", 39 | "files": [ 40 | "README.md", 41 | "dist", 42 | "src", 43 | "!*/__tests__" 44 | ], 45 | "peerDependencies": { 46 | "@loopback/core": "^6.1.6" 47 | }, 48 | "dependencies": { 49 | "@loopback/repository": "^7.0.9", 50 | "@loopback/rest": "^14.0.9", 51 | "tslib": "^2.6.2" 52 | }, 53 | "devDependencies": { 54 | "@loopback/build": "^11.0.8", 55 | "@loopback/core": "^6.1.6", 56 | "@commitlint/cli": "^17.8.1", 57 | "@commitlint/config-conventional": "^17.8.1", 58 | "@loopback/eslint-config": "^15.0.4", 59 | "@loopback/testlab": "^7.0.8", 60 | "@semantic-release/changelog": "^6.0.3", 61 | "@semantic-release/commit-analyzer": "^11.1.0", 62 | "@semantic-release/git": "^10.0.1", 63 | "@semantic-release/npm": "^11.0.3", 64 | "@semantic-release/release-notes-generator": "^12.1.0", 65 | "commitizen": "^4.3.0", 66 | "cz-conventional-changelog": "^3.3.0", 67 | "cz-customizable": "^6.9.2", 68 | "cz-customizable-ghooks": "^2.0.0", 69 | "semantic-release": " ^23.0.2", 70 | "@types/node": "^18.11.9", 71 | "eslint": "^8.57.0", 72 | "husky": "^7.0.4", 73 | "source-map-support": "^0.5.21", 74 | "typescript": "~5.2.2" 75 | }, 76 | "publishConfig": { 77 | "registry": "https://registry.npmjs.org/" 78 | }, 79 | "config": { 80 | "commitizen": { 81 | "path": "./node_modules/cz-customizable" 82 | } 83 | }, 84 | "release": { 85 | "branches": [ 86 | "master" 87 | ], 88 | "plugins": [ 89 | [ 90 | "@semantic-release/commit-analyzer", 91 | { 92 | "preset": "angular", 93 | "releaseRules": [ 94 | { 95 | "type": "chore", 96 | "scope": "deps", 97 | "release": "patch" 98 | } 99 | ] 100 | } 101 | ], 102 | "@semantic-release/release-notes-generator", 103 | "@semantic-release/changelog", 104 | "@semantic-release/npm", 105 | [ 106 | "@semantic-release/git", 107 | { 108 | "assets": [ 109 | "package.json", 110 | "CHANGELOG.md" 111 | ], 112 | "message": "chore(release): ${nextRelease.version} semantic" 113 | } 114 | ], 115 | "@semantic-release/github" 116 | ], 117 | "repositoryUrl": "git@github.com:sourcefuse/loopBack4-dynamic-datasource.git" 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/__tests__/acceptance/README.md: -------------------------------------------------------------------------------- 1 | # Acceptance tests 2 | -------------------------------------------------------------------------------- /src/__tests__/integration/README.md: -------------------------------------------------------------------------------- 1 | # Integration tests 2 | -------------------------------------------------------------------------------- /src/__tests__/unit/README.md: -------------------------------------------------------------------------------- 1 | # Unit tests 2 | -------------------------------------------------------------------------------- /src/component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Application, 3 | Component, 4 | config, 5 | ContextTags, 6 | CoreBindings, 7 | inject, 8 | injectable, 9 | ProviderMap, 10 | } from '@loopback/core'; 11 | import { 12 | DynamicDatasourceBindings, 13 | Loopback4DynamicDatasourceComponentBindings, 14 | } from './keys'; 15 | import { 16 | DEFAULT_LOOPBACK4_DYNAMIC_DATASOURCE_OPTIONS, 17 | Loopback4DynamicDatasourceComponentOptions, 18 | } from './types'; 19 | import { 20 | DatasourceIdentifierProvider, 21 | DatasourceProvider, 22 | DynamicDatasourceActionProvider, 23 | DynamicDatasourceMiddlewareProvider, 24 | } from './providers'; 25 | 26 | // Configure the binding for Loopback4DynamicDatasourceComponent 27 | @injectable({ 28 | tags: { 29 | [ContextTags.KEY]: Loopback4DynamicDatasourceComponentBindings.COMPONENT, 30 | }, 31 | }) 32 | export class Loopback4DynamicDatasourceComponent implements Component { 33 | providers: ProviderMap = { 34 | [DynamicDatasourceBindings.DYNAMIC_DATASOURCE_ACTION.key]: 35 | DynamicDatasourceActionProvider, 36 | [DynamicDatasourceBindings.DATASOURCE_PROVIDER.key]: DatasourceProvider, 37 | [DynamicDatasourceBindings.DATASOURCE_IDENTIFIER_PROVIDER.key]: 38 | DatasourceIdentifierProvider, 39 | [DynamicDatasourceBindings.MIDDLEWARE.key]: 40 | DynamicDatasourceMiddlewareProvider, 41 | }; 42 | 43 | constructor( 44 | @inject(CoreBindings.APPLICATION_INSTANCE) 45 | private application: Application, 46 | @config() 47 | private options: Loopback4DynamicDatasourceComponentOptions = DEFAULT_LOOPBACK4_DYNAMIC_DATASOURCE_OPTIONS, 48 | ) {} 49 | } 50 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component'; 2 | export * from './keys'; 3 | export * from './types'; 4 | export * from './providers'; 5 | -------------------------------------------------------------------------------- /src/keys.ts: -------------------------------------------------------------------------------- 1 | import {BindingKey, CoreBindings} from '@loopback/core'; 2 | import {Loopback4DynamicDatasourceComponent} from './component'; 3 | import { 4 | DatasourceIdentifier, 5 | DatasourceIdentifierFn, 6 | DatasourceProviderFn, 7 | SetupDatasourceFn, 8 | } from './types'; 9 | import {Middleware} from '@loopback/rest'; 10 | 11 | /** 12 | * Binding keys used by this component. 13 | */ 14 | export namespace Loopback4DynamicDatasourceComponentBindings { 15 | export const COMPONENT = 16 | BindingKey.create( 17 | `${CoreBindings.COMPONENTS}.Loopback4DynamicDatasourceComponent`, 18 | ); 19 | } 20 | 21 | export namespace DynamicDatasourceBindings { 22 | export const MIDDLEWARE = BindingKey.create( 23 | 'middleware.dynamic-datasource', 24 | ); 25 | 26 | export const DYNAMIC_DATASOURCE_ACTION = BindingKey.create( 27 | 'dynamic-datasource.action', 28 | ); 29 | 30 | export const CURRENT_INDETIFIER = BindingKey.create( 31 | 'dynamic-datasource.currentIdentifier', 32 | ); 33 | 34 | export const DATASOURCE_IDENTIFIER_PROVIDER = 35 | BindingKey.create( 36 | 'dynamic-datasource.action.databaseIdentifier', 37 | ); 38 | 39 | export const DATASOURCE_PROVIDER = BindingKey.create( 40 | 'dynamic-datasource.action.datasourceProvider', 41 | ); 42 | } 43 | 44 | export const DYNAMIC_DATASOURCE = 'dynamic-datasource'; 45 | -------------------------------------------------------------------------------- /src/providers/README.md: -------------------------------------------------------------------------------- 1 | # Providers 2 | 3 | This directory contains providers contributing additional bindings, for example 4 | custom sequence actions. 5 | 6 | ## Overview 7 | 8 | A [provider](http://loopback.io/doc/en/lb4/Creating-components.html#providers) 9 | is a class that provides a `value()` function. This function is called `Context` 10 | when another entity requests a value to be injected. 11 | 12 | Here we create a provider for a logging function that can be used as a new 13 | action in a custom [sequence](http://loopback.io/doc/en/lb4/Sequence.html). 14 | 15 | The logger will log the URL, the parsed request parameters, and the result. The 16 | logger is also capable of timing the sequence if you start a timer at the start 17 | of the sequence using `this.logger.startTimer()`. 18 | 19 | ## Basic Usage 20 | 21 | ### TimerProvider 22 | 23 | TimerProvider is automatically bound to your Application's 24 | [Context](http://loopback.io/doc/en/lb4/Context.html) using the LogComponent 25 | which exports this provider with a binding key of `extension-starter.timer`. You 26 | can learn more about components in the 27 | [related resources section](#related-resources). 28 | 29 | This provider makes availble to your application a timer function which given a 30 | start time _(given as an array [seconds, nanoseconds])_ can give you a total 31 | time elapsed since the start in milliseconds. The timer can also start timing if 32 | no start time is given. This is used by LogComponent to allow a user to time a 33 | Sequence. 34 | 35 | _NOTE:_ _You can get the start time in the required format by using 36 | `this.logger.startTimer()`._ 37 | 38 | You can provide your own implementation of the elapsed time function by binding 39 | it to the binding key (accessible via `ExtensionStarterBindings`) as follows: 40 | 41 | ```ts 42 | app.bind(ExtensionStarterBindings.TIMER).to(timerFn); 43 | ``` 44 | 45 | ### LogProvider 46 | 47 | LogProvider can automatically be bound to your Application's Context using the 48 | LogComponent which exports the provider with a binding key of 49 | `extension-starter.actions.log`. 50 | 51 | The key can be accessed by importing `ExtensionStarterBindings` as follows: 52 | 53 | **Example: Binding Keys** 54 | 55 | ```ts 56 | import {ExtensionStarterBindings} from 'HelloExtensions'; 57 | // Key can be accessed as follows now 58 | const key = ExtensionStarterBindings.LOG_ACTION; 59 | ``` 60 | 61 | LogProvider gives us a seuqence action and a `startTimer` function. In order to 62 | use the sequence action, you must define your own sequence as shown below. 63 | 64 | **Example: Sequence** 65 | 66 | ```ts 67 | class LogSequence implements SequenceHandler { 68 | constructor( 69 | @inject(coreSequenceActions.FIND_ROUTE) protected findRoute: FindRoute, 70 | @inject(coreSequenceActions.PARSE_PARAMS) 71 | protected parseParams: ParseParams, 72 | @inject(coreSequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, 73 | @inject(coreSequenceActions.SEND) protected send: Send, 74 | @inject(coreSequenceActions.REJECT) protected reject: Reject, 75 | // We get the logger injected by the LogProvider here 76 | @inject(ExtensionStarterBindings.LOG_ACTION) protected logger: LogFn, 77 | ) {} 78 | 79 | async handle(context: RequestContext) { 80 | const {request, response} = context; 81 | 82 | // We define these variable outside so they can be accessed by logger. 83 | let args: any = []; 84 | let result: any; 85 | 86 | // Optionally start timing the sequence using the timer 87 | // function available via LogFn 88 | const start = this.logger.startTimer(); 89 | 90 | try { 91 | const route = this.findRoute(request); 92 | args = await this.parseParams(request, route); 93 | result = await this.invoke(route, args); 94 | this.send(response, result); 95 | } catch (error) { 96 | result = error; // so we can log the error message in the logger 97 | this.reject(context, error); 98 | } 99 | 100 | // We call the logger function given to us by LogProvider 101 | this.logger(request, args, result, start); 102 | } 103 | } 104 | ``` 105 | 106 | Once a sequence has been written, we can just use that in our Application as 107 | follows: 108 | 109 | **Example: Application** 110 | 111 | ```ts 112 | const app = new Application({ 113 | sequence: LogSequence, 114 | }); 115 | app.component(LogComponent); 116 | 117 | // Now all requests handled by our sequence will be logged. 118 | ``` 119 | 120 | ## Related Resources 121 | 122 | You can check out the following resource to learn more about providers, 123 | components, sequences, and binding keys. 124 | 125 | - [Providers](http://loopback.io/doc/en/lb4/Creating-components.html#providers) 126 | - [Creating Components](http://loopback.io/doc/en/lb4/Creating-components.html) 127 | - [Using Components](http://loopback.io/doc/en/lb4/Components.html) 128 | - [Sequence](http://loopback.io/doc/en/lb4/Sequence.html) 129 | - [Binding Keys](http://loopback.io/doc/en/lb4/Decorators.html) 130 | -------------------------------------------------------------------------------- /src/providers/datasource-identifier.provider.ts: -------------------------------------------------------------------------------- 1 | import {Provider} from '@loopback/core'; 2 | import {DatasourceIdentifierFn} from '../types'; 3 | 4 | export class DatasourceIdentifierProvider 5 | implements Provider 6 | { 7 | constructor() {} 8 | 9 | value(): DatasourceIdentifierFn { 10 | return async () => { 11 | return null; 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/providers/datasource.provider.ts: -------------------------------------------------------------------------------- 1 | import {Provider} from '@loopback/core'; 2 | import {DatasourceProviderFn} from '../types'; 3 | 4 | export class DatasourceProvider implements Provider { 5 | constructor() {} 6 | 7 | value(): DatasourceProviderFn { 8 | return async () => { 9 | return {}; 10 | }; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/providers/dynamic-datasource-action.provider.ts: -------------------------------------------------------------------------------- 1 | import {CoreBindings, inject, injectable, Provider} from '@loopback/core'; 2 | import { 3 | asMiddleware, 4 | ExpressRequestHandler, 5 | getMiddlewareContext, 6 | HttpErrors, 7 | Middleware, 8 | RequestContext, 9 | RestApplication, 10 | RestMiddlewareGroups, 11 | } from '@loopback/rest'; 12 | import {RepositoryBindings, RepositoryTags} from '@loopback/repository'; 13 | import {DynamicDatasourceBindings} from '../keys'; 14 | import { 15 | DatasourceIdentifierFn, 16 | DatasourceProviderFn, 17 | SetupDatasourceFn, 18 | } from '../types'; 19 | 20 | export class DynamicDatasourceActionProvider 21 | implements Provider 22 | { 23 | constructor( 24 | @inject(CoreBindings.APPLICATION_INSTANCE) 25 | private application: RestApplication, 26 | @inject(DynamicDatasourceBindings.DATASOURCE_IDENTIFIER_PROVIDER) 27 | private readonly getDatasourceIdentifier: DatasourceIdentifierFn, 28 | @inject(DynamicDatasourceBindings.DATASOURCE_PROVIDER) 29 | private readonly getDatasourceProvider: DatasourceProviderFn, 30 | ) {} 31 | 32 | value(): SetupDatasourceFn { 33 | return async ctx => { 34 | await this.action(ctx as RequestContext); 35 | }; 36 | } 37 | 38 | async action(requestCtx: RequestContext) { 39 | const datasourceIdentifier = await this.bindDatabaseIdentifier(requestCtx); 40 | if (datasourceIdentifier == null) return; 41 | const datasourceProvider = 42 | await this.getDatasourceProvider(datasourceIdentifier); 43 | const promises = Object.entries(datasourceProvider).map(async value => { 44 | const [key, method] = value; 45 | const dbBindKey = `${RepositoryBindings.DATASOURCES}.${key}.${datasourceIdentifier.id}`; 46 | if (!this.application.isBound(dbBindKey)) { 47 | const datasource = await method(); 48 | this.application 49 | .bind(dbBindKey) 50 | .to(datasource) 51 | .tag(RepositoryTags.DATASOURCE); 52 | } 53 | requestCtx 54 | .bind(`${RepositoryBindings.DATASOURCES}.${key}`) 55 | .toAlias(dbBindKey); 56 | }); 57 | await Promise.all(promises); 58 | } 59 | 60 | private async bindDatabaseIdentifier(requestCtx: RequestContext) { 61 | const datasourceIdentifier = await this.getDatasourceIdentifier(requestCtx); 62 | if (datasourceIdentifier) { 63 | requestCtx 64 | .bind(DynamicDatasourceBindings.CURRENT_INDETIFIER) 65 | .to(datasourceIdentifier); 66 | } 67 | return datasourceIdentifier; 68 | } 69 | } 70 | 71 | @injectable( 72 | asMiddleware({ 73 | group: RestMiddlewareGroups.MIDDLEWARE, 74 | downstreamGroups: RestMiddlewareGroups.FIND_ROUTE, 75 | }), 76 | ) 77 | export class DynamicDatasourceMiddlewareProvider 78 | implements Provider 79 | { 80 | constructor( 81 | @inject(DynamicDatasourceBindings.DYNAMIC_DATASOURCE_ACTION) 82 | private readonly setupDatasource: SetupDatasourceFn, 83 | ) {} 84 | value(): Middleware { 85 | return async (ctx, next) => { 86 | await this.setupDatasource(ctx as RequestContext); 87 | return next(); 88 | }; 89 | } 90 | } 91 | 92 | //This middleware function can be used with express middlewares to get invoked with express them. 93 | /** 94 | * @param req Request object 95 | * @param res Response object 96 | * @param next Method 97 | * @returns Calls Next Method 98 | */ 99 | // eslint-disable-next-line @typescript-eslint/no-misused-promises 100 | export const setupDataSourceMiddlewareFunction: ExpressRequestHandler = async ( 101 | req, 102 | res, 103 | next, 104 | ) => { 105 | const reqCtx = getMiddlewareContext(req); 106 | if (!reqCtx) { 107 | throw new HttpErrors.InternalServerError( 108 | 'Request context is not available.', 109 | ); 110 | } 111 | const setupDatasource = await reqCtx.get( 112 | DynamicDatasourceBindings.DYNAMIC_DATASOURCE_ACTION, 113 | ); 114 | if (!setupDatasource) { 115 | throw new HttpErrors.InternalServerError( 116 | 'setupDatasource function not found.', 117 | ); 118 | } 119 | await setupDatasource(reqCtx); 120 | if (next) return next(); 121 | else return; 122 | }; 123 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './datasource-identifier.provider'; 2 | export * from './datasource.provider'; 3 | export * from './dynamic-datasource-action.provider'; 4 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface defining the component's options object 3 | */ 4 | import {RequestContext} from '@loopback/rest'; 5 | import {juggler} from '@loopback/repository'; 6 | 7 | export interface Loopback4DynamicDatasourceComponentOptions { 8 | // Add the definitions here 9 | } 10 | 11 | /** 12 | * Default options for the component 13 | */ 14 | export const DEFAULT_LOOPBACK4_DYNAMIC_DATASOURCE_OPTIONS: Loopback4DynamicDatasourceComponentOptions = 15 | { 16 | // Specify the values here 17 | }; 18 | 19 | export interface DatasourceIdentifier { 20 | id: string; 21 | [attribute: string]: unknown; 22 | } 23 | 24 | export interface DatasourceProviderFn { 25 | (datasourceIdentifier: DatasourceIdentifier): Promise<{ 26 | [prop: string]: () => Promise; 27 | }>; 28 | } 29 | 30 | export interface DatasourceIdentifierFn { 31 | (requestCtx: RequestContext): Promise; 32 | } 33 | 34 | export interface SetupDatasourceFn { 35 | (requestCtx: RequestContext): Promise; 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "@loopback/build/config/tsconfig.common.json", 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src"] 9 | } 10 | --------------------------------------------------------------------------------