├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── autoapproval.yml └── workflows │ ├── codeql-analysis.yml │ ├── gh-actions-cache.yml │ ├── main.yml │ └── size.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── example ├── .eslintrc ├── README.md ├── nodemon.json ├── package-lock.json ├── package.json ├── prisma │ ├── migrations │ │ ├── 20220407135605_initial_migration │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma ├── src │ └── index.ts └── tsconfig.json ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── async-cache-dedupe.d.ts ├── cacheMethods.ts ├── index.ts ├── prisma-client.d.ts └── types.ts ├── test └── index.test.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, since Windows-based GitHub actions are cloning with CRLF as default 2 | * text eol=lf 3 | -------------------------------------------------------------------------------- /.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 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/autoapproval.yml: -------------------------------------------------------------------------------- 1 | from_owner: 2 | - dependabot-preview[bot] 3 | - dependabot[bot] 4 | required_labels: 5 | - dependencies 6 | apply_labels: 7 | - autoapproved 8 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | schedule: 9 | - cron: "38 18 * * 3" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze Code Quality 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: ["javascript", "typescript"] 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | 34 | - name: Autobuild 35 | uses: github/codeql-action/autobuild@v2 36 | 37 | - name: Perform CodeQL Analysis 38 | uses: github/codeql-action/analyze@v2 39 | -------------------------------------------------------------------------------- /.github/workflows/gh-actions-cache.yml: -------------------------------------------------------------------------------- 1 | name: Cleanup caches by a branch 2 | on: 3 | pull_request: 4 | types: 5 | - closed 6 | workflow_dispatch: 7 | 8 | jobs: 9 | cleanup: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | 15 | - name: Cleanup 16 | run: | 17 | gh extension install actions/gh-actions-cache 18 | 19 | REPO=${{ github.repository }} 20 | BRANCH=${{ github.ref }} 21 | 22 | echo "Fetching list of cache key" 23 | cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) 24 | 25 | ## Setting this to not fail the workflow while deleting cache keys. 26 | set +e 27 | echo "Deleting caches..." 28 | for cacheKey in $cacheKeysForPR 29 | do 30 | gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm 31 | done 32 | echo "Done" 33 | env: 34 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: "Main Workflow" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | concurrency: ${{ github.workflow }}-${{ github.ref }} 12 | 13 | jobs: 14 | build: 15 | name: "[Build] Node.js ${{ matrix.node }} and ${{ matrix.os }}" 16 | 17 | strategy: 18 | matrix: 19 | node: ["16", "18"] 20 | os: [ubuntu-latest, windows-latest, macOS-latest] 21 | 22 | runs-on: ${{ matrix.os }} 23 | 24 | steps: 25 | - name: Checkout repo 26 | uses: actions/checkout@v4 27 | 28 | - name: Setup Node.js ${{ matrix.node }} 29 | uses: actions/setup-node@v4 30 | with: 31 | node-version: ${{ matrix.node }} 32 | cache: "npm" 33 | - run: npm i -g npm@7 34 | 35 | - name: Install with npm 36 | run: npm i 37 | 38 | - name: Build 39 | run: npm run build 40 | 41 | test: 42 | runs-on: ubuntu-latest 43 | name: "[Test] Node.js 16 and ubuntu-latest" 44 | steps: 45 | - name: Checkout repo 46 | uses: actions/checkout@v4 47 | 48 | - name: Setup Node.js 16 49 | uses: actions/setup-node@v4 50 | with: 51 | node-version: 16 52 | cache: "npm" 53 | 54 | - name: Install with npm 55 | run: npm install 56 | 57 | - name: Run tests 58 | run: npm run test 59 | 60 | - name: Generate coverage report 61 | run: npm run coverage 62 | 63 | - name: Upload coverage to Codecov 64 | uses: codecov/codecov-action@v3 65 | with: 66 | token: ${{ secrets.CODECOV_TOKEN }} 67 | -------------------------------------------------------------------------------- /.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: Library code size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Ignore Prisma SQLite database 107 | dev.db 108 | dev.db-journal 109 | 110 | # IntelliJ IDEA project files 111 | .idea 112 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .github 3 | .nyc_output 4 | .DS_Store 5 | node_modules 6 | test 7 | renovate.json 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | fund=false 3 | audit=false 4 | npm_config_loglevel=error 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | dist/ 5 | 6 | # Ignore files for npm 7 | package-lock.json 8 | 9 | # [error] src/prisma-client.d.ts: SyntaxError: Export 'Prisma' is not defined. (44:11) 10 | src/prisma-client.d.ts 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "plugins": ["@ianvs/prettier-plugin-sort-imports"], 4 | "arrowParens": "always", 5 | "bracketSameLine": false, 6 | "bracketSpacing": true, 7 | "endOfLine": "lf", 8 | "htmlWhitespaceSensitivity": "strict", 9 | "jsxSingleQuote": false, 10 | "printWidth": 120, 11 | "proseWrap": "always", 12 | "quoteProps": "consistent", 13 | "semi": true, 14 | "singleAttributePerLine": true, 15 | "singleQuote": false, 16 | "tabWidth": 2, 17 | "trailingComma": "all", 18 | "useTabs": false, 19 | "importOrder": [ 20 | "^(prisma$)|^(prisma/(.*)$)", 21 | "^(vitest$)|^(vitest/(.*)$)", 22 | "^(ioredis$)|^(ioredis/(.*)$)", 23 | "", 24 | "^types$", 25 | "^[./]" 26 | ], 27 | "importOrderSeparation": true, 28 | "importOrderSortSpecifiers": true, 29 | "importOrderBuiltinModulesToTop": true, 30 | "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"], 31 | "importOrderMergeDuplicateImports": true, 32 | "importOrderCombineTypeAndValueImports": true 33 | } 34 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | asjas@hey.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | [Prisma Redis Middleware] Copyright (2023) A-J Roos (“Licensor”) 2 | 3 | **HIPPOCRATIC LICENSE** 4 | 5 | **Version 3.0, October 2021** 6 | 7 | **TERMS AND CONDITIONS** 8 | 9 | TERMS AND CONDITIONS FOR USE, COPY, MODIFICATION, PREPARATION OF DERIVATIVE WORK, REPRODUCTION, AND DISTRIBUTION: 10 | 11 | 1. **DEFINITIONS:** 12 | 13 | _This section defines certain terms used throughout this license agreement._ 14 | 15 | 1.1. "License" means the terms and conditions, as stated herein, for use, copy, modification, preparation of derivative 16 | work, reproduction, and distribution of Software (as defined below). 17 | 18 | 1.2. "Licensor" means the copyright and/or patent owner or entity authorized by the copyright and/or patent owner that 19 | is granting the License. 20 | 21 | 1.3. "Licensee" means the individual or entity exercising permissions granted by this License, including the use, copy, 22 | modification, preparation of derivative work, reproduction, and distribution of Software (as defined below). 23 | 24 | 1.4. "Software" means any copyrighted work, including but not limited to software code, authored by Licensor and made 25 | available under this License. 26 | 27 | 1.5. "Supply Chain" means the sequence of processes involved in the production and/or distribution of a commodity, good, 28 | or service offered by the Licensee. 29 | 30 | 1.6. "Supply Chain Impacted Party" or "Supply Chain Impacted Parties" means any person(s) directly impacted by any of 31 | Licensee's Supply Chain, including the practices of all persons or entities within the Supply Chain prior to a good or 32 | service reaching the Licensee. 33 | 34 | 1.7. "Duty of Care" is defined by its use in tort law, delict law, and/or similar bodies of law closely related to tort 35 | and/or delict law, including without limitation, a requirement to act with the watchfulness, attention, caution, and 36 | prudence that a reasonable person in the same or similar circumstances would use towards any Supply Chain Impacted 37 | Party. 38 | 39 | 1.8. "Worker" is defined to include any and all permanent, temporary, and agency workers, as well as piece-rate, 40 | salaried, hourly paid, legal young (minors), part-time, night, and migrant workers. 41 | 42 | 2. **INTELLECTUAL PROPERTY GRANTS:** 43 | 44 | _This section identifies intellectual property rights granted to a Licensee_. 45 | 46 | 2.1. _Grant of Copyright License_: Subject to the terms and conditions of this License, Licensor hereby grants to 47 | Licensee a worldwide, non-exclusive, no-charge, royalty-free copyright license to use, copy, modify, prepare derivative 48 | work, reproduce, or distribute the Software, Licensor authored modified software, or other work derived from the 49 | Software. 50 | 51 | 2.2 _Grant of Patent License_: Subject to the terms and conditions of this License, Licensor hereby grants Licensee a 52 | worldwide, non-exclusive, no-charge, royalty-free patent license to make, have made, use, offer to sell, sell, import, 53 | and otherwise transfer Software. 54 | 55 | 3. **ETHICAL STANDARDS:** 56 | 57 | _This section lists conditions the Licensee must comply with in order to have rights under this License._ 58 | 59 | The rights granted to the Licensee by this License are expressly made subject to the Licensee's ongoing compliance with 60 | the following conditions: 61 | 62 | 3.1. The Licensee SHALL NOT, whether directly or indirectly, through agents or assigns: 63 | 64 | 3.1.1. Infringe upon any person's right to life or security of person, engage in extrajudicial killings, or commit murder, without lawful cause 65 | (See Article 3, *United Nations Universal Declaration of Human Rights*; Article 6, *International Covenant on Civil and Political Rights*) 66 | 67 | 3.1.2. Hold any person in slavery, servitude, or forced labor 68 | (See Article 4, *United Nations Universal Declaration of Human Rights*; Article 8, *International Covenant on Civil and Political Rights*); 69 | 70 | 3.1.3. Contribute to the institution of slavery, slave trading, forced labor, or unlawful child labor 71 | (See Article 4, *United Nations Universal Declaration of Human Rights*; Article 8, *International Covenant on Civil and Political Rights*); 72 | 73 | 3.1.4. Torture or subject any person to cruel, inhumane, or degrading treatment or punishment 74 | (See Article 5, *United Nations Universal Declaration of Human Rights*; Article 7, *International Covenant on Civil and Political Rights*); 75 | 76 | 3.1.5. Discriminate on the basis of sex, gender, sexual orientation, race, ethnicity, nationality, religion, caste, age, medical disability or impairment, and/or any other like circumstances 77 | (See Article 7, *United Nations Universal Declaration of Human Rights*; Article 2, *International Covenant on Economic, Social and Cultural Rights*; Article 26, *International Covenant on Civil and Political Rights*); 78 | 79 | 3.1.6. Prevent any person from exercising his/her/their right to seek an effective remedy by a competent court or national tribunal (including domestic judicial systems, international courts, arbitration bodies, and other adjudicating bodies) for actions violating the fundamental rights granted to him/her/them by applicable constitutions, applicable laws, or by this License 80 | (See Article 8, *United Nations Universal Declaration of Human Rights*; Articles 9 and 14, *International Covenant on Civil and Political Rights*); 81 | 82 | 3.1.7. Subject any person to arbitrary arrest, detention, or exile 83 | (See Article 9, *United Nations Universal Declaration of Human Rights*; Article 9, *International Covenant on Civil and Political Rights*); 84 | 85 | 3.1.8. Subject any person to arbitrary interference with a person's privacy, family, home, or correspondence without the express written consent of the person 86 | (See Article 12, *United Nations Universal Declaration of Human Rights*; Article 17, *International Covenant on Civil and Political Rights*); 87 | 88 | 3.1.9. Arbitrarily deprive any person of his/her/their property 89 | (See Article 17, *United Nations Universal Declaration of Human Rights*); 90 | 91 | 3.1.10. Forcibly remove indigenous peoples from their lands or territories or take any action with the aim or effect of dispossessing indigenous peoples from their lands, territories, or resources, including without limitation the intellectual property or traditional knowledge of indigenous peoples, without the free, prior, and informed consent of indigenous peoples concerned 92 | (See Articles 8 and 10, *United Nations Declaration on the Rights of Indigenous Peoples*); 93 | 94 | 3.1.11. (Module -- Carbon Underground 200) Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, on the FFI Solutions Carbon Underground 200 list; 95 | 96 | 3.1.12. (Module -- Ecocide) Commit ecocide: 97 | 98 | 3.1.12.1 For the purpose of this section, "ecocide" means unlawful or wanton acts committed with knowledge that there is a substantial likelihood of severe and either widespread or long-term damage to the environment being caused by those acts; 99 | 100 | 3.1.12.2 For the purpose of further defining ecocide and the terms contained in the previous paragraph: 101 | 102 | 3.1.12.2.1. "Wanton" means with reckless disregard for damage which would be clearly excessive in relation to the social and economic benefits anticipated; 103 | 104 | 3.1.12.2.2. "Severe" means damage which involves very serious adverse changes, disruption, or harm to any element of the environment, including grave impacts on human life or natural, cultural, or economic resources; 105 | 106 | 3.1.12.2.3. "Widespread" means damage which extends beyond a limited geographic area, crosses state boundaries, or is suffered by an entire ecosystem or species or a large number of human beings; 107 | 108 | 3.1.12.2.4. "Long-term" means damage which is irreversible or which cannot be redressed through natural recovery within a reasonable period of time; and 109 | 110 | 3.1.12.2.5. "Environment" means the earth, its biosphere, cryosphere, lithosphere, hydrosphere, and atmosphere, as well as outer space 111 | 112 | (See Section II, *Independent Expert Panel for the Legal Definition of Ecocide*, Stop Ecocide Foundation and the Promise Institute for Human Rights at UCLA School of Law, June 2021); 113 | 114 | 3.1.13. (Module -- Extractive Industries) Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that engages in fossil fuel or mineral exploration, extraction, development, or sale; 115 | 116 | 3.1.14. (Module -- BDS) Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, identified by the Boycott, Divestment, Sanctions ("BDS") movement on its website ([https://bdsmovement.net/](https://bdsmovement.net/) and [https://bdsmovement.net/get-involved/what-to-boycott](https://bdsmovement.net/get-involved/what-to-boycott)) as a target for boycott; 117 | 118 | 3.1.15. (Module -- Taliban) Be an individual or entity that: 119 | 120 | 3.1.15.1. engages in any commercial transactions with the Taliban; or 121 | 122 | 3.1.15.2. is a representative, agent, affiliate, successor, attorney, or assign of the Taliban; 123 | 124 | 3.1.16. (Module -- Myanmar) Be an individual or entity that: 125 | 126 | 3.1.16.1. engages in any commercial transactions with the Myanmar/Burmese military junta; or 127 | 128 | 3.1.16.2. is a representative, agent, affiliate, successor, attorney, or assign of the Myanmar/Burmese government; 129 | 130 | 3.1.17. (Module -- Xinjiang Uygur Autonomous Region) Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of any individual or entity, that does business in, purchases goods from, or otherwise benefits from goods produced in the Xinjiang Uygur Autonomous Region of China; 131 | 132 | 3.1.18. (Module -- U.S. Tariff Act) Be an individual or entity: 133 | 134 | 3.1.18.1. which U.S. Customs and Border Protection (CBP) has currently issued a Withhold Release Order (WRO) or finding against based on reasonable suspicion of forced labor; or 135 | 136 | 3.1.18.2. that is a representative, agent, affiliate, successor, attorney, or assign of an individual or entity that does business with an individual or entity which currently has a WRO or finding from CBP issued against it based on reasonable suspicion of forced labor; 137 | 138 | 3.1.19. (Module -- Mass Surveillance) Be a government agency or multinational corporation, or a representative, agent, affiliate, successor, attorney, or assign of a government or multinational corporation, which participates in mass surveillance programs; 139 | 140 | 3.1.20. (Module -- Military Activities) Be an entity or a representative, agent, affiliate, successor, attorney, or assign of an entity which conducts military activities; 141 | 142 | 3.1.21. (Module -- Law Enforcement) Be an individual or entity, or a or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that provides good or services to, or otherwise enters into any commercial contracts with, any local, state, or federal law enforcement agency; 143 | 144 | 3.1.22. (Module -- Media) Be an individual or entity, or a or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that broadcasts messages promoting killing, torture, or other forms of extreme violence; 145 | 146 | 3.1.23. Interfere with Workers' free exercise of the right to organize and associate 147 | (See Article 20, United Nations Universal Declaration of Human Rights; C087 - Freedom of Association and Protection of the Right to Organise Convention, 1948 (No. 87), International Labour Organization; Article 8, International Covenant on Economic, Social and Cultural Rights); and 148 | 149 | 3.1.24. Harm the environment in a manner inconsistent with local, state, national, or international law. 150 | 151 | 3.2. The Licensee SHALL: 152 | 153 | 3.2.1. (Module -- Social Auditing) Only use social auditing mechanisms that adhere to Worker-Driven Social Responsibility Network's Statement of Principles (https://wsr-network.org/what-is-wsr/statement-of-principles/) over traditional social auditing mechanisms, to the extent the Licensee uses any social auditing mechanisms at all; 154 | 155 | 3.2.2. (Module -- Workers on Board of Directors) Ensure that if the Licensee has a Board of Directors, 30% of Licensee's board seats are held by Workers paid no more than 200% of the compensation of the lowest paid Worker of the Licensee; 156 | 157 | 3.2.3. (Module -- Supply Chain Transparency) Provide clear, accessible supply chain data to the public in accordance with the following conditions: 158 | 159 | 3.2.3.1. All data will be on Licensee's website and/or, to the extent Licensee is a representative, agent, affiliate, successor, attorney, subsidiary, or assign, on Licensee's principal's or parent's website or some other online platform accessible to the public via an internet search on a common internet search engine; and 160 | 161 | 3.2.3.2. Data published will include, where applicable, manufacturers, top tier suppliers, subcontractors, cooperatives, component parts producers, and farms; 162 | 163 | 3.2.4. Provide equal pay for equal work where the performance of such work requires equal skill, effort, and responsibility, and which are performed under similar working conditions, except where such payment is made pursuant to: 164 | 165 | 3.2.4.1. A seniority system; 166 | 167 | 3.2.4.2. A merit system; 168 | 169 | 3.2.4.3. A system which measures earnings by quantity or quality of production; or 170 | 171 | 3.2.4.4. A differential based on any other factor other than sex, gender, sexual orientation, race, ethnicity, nationality, religion, caste, age, medical disability or impairment, and/or any other like circumstances 172 | (See 29 U.S.C.A. � 206(d)(1); Article 23, *United Nations Universal Declaration of Human Rights*; Article 7, *International Covenant on Economic, Social and Cultural Rights*; Article 26, *International Covenant on Civil and Political Rights*); and 173 | 174 | 3.2.5. Allow for reasonable limitation of working hours and periodic holidays with pay 175 | (See Article 24, *United Nations Universal Declaration of Human Rights*; Article 7, *International Covenant on Economic, Social and Cultural Rights*). 176 | 177 | 4. **SUPPLY CHAIN IMPACTED PARTIES:** 178 | 179 | _This section identifies additional individuals or entities that a Licensee could harm as a result of violating the 180 | Ethical Standards section, the condition that the Licensee must voluntarily accept a Duty of Care for those individuals 181 | or entities, and the right to a private right of action that those individuals or entities possess as a result of 182 | violations of the Ethical Standards section._ 183 | 184 | 4.1. In addition to the above Ethical Standards, Licensee voluntarily accepts a Duty of Care for Supply Chain Impacted Parties of this License, including individuals and communities impacted by violations of the Ethical Standards. The Duty of Care is breached when a provision within the Ethical Standards section is violated by a Licensee, one of its successors or assigns, or by an individual or entity that exists within the Supply Chain prior to a good or service reaching the Licensee. 185 | 186 | 4.2. Breaches of the Duty of Care, as stated within this section, shall create a private right of action, allowing any Supply Chain Impacted Party harmed by the Licensee to take legal action against the Licensee in accordance with applicable negligence laws, whether they be in tort law, delict law, and/or similar bodies of law closely related to tort and/or delict law, regardless if Licensee is directly responsible for the harms suffered by a Supply Chain Impacted Party. Nothing in this section shall be interpreted to include acts committed by individuals outside of the scope of his/her/their employment. 187 | 188 | 5. **NOTICE:** _This section explains when a Licensee must notify others of the License._ 189 | 190 | 5.1. _Distribution of Notice_: Licensee must ensure that everyone who receives a copy of or uses any part of 191 | Software from Licensee, with or without changes, also receives the License and the copyright notice included with 192 | Software (and if included by the Licensor, patent, trademark, and attribution notice). Licensee must ensure that 193 | License is prominently displayed so that any individual or entity seeking to download, copy, use, or otherwise 194 | receive any part of Software from Licensee is notified of this License and its terms and conditions. Licensee must 195 | cause any modified versions of the Software to carry prominent notices stating that Licensee changed the Software. 196 | 197 | 5.2. _Modified Software_: Licensee is free to create modifications of the Software and distribute only the modified 198 | portion created by Licensee, however, any derivative work stemming from the Software or its code must be distributed 199 | pursuant to this License, including this Notice provision. 200 | 201 | 5.3. _Recipients as Licensees_: Any individual or entity that uses, copies, modifies, reproduces, distributes, or 202 | prepares derivative work based upon the Software, all or part of the Software's code, or a derivative work developed 203 | by using the Software, including a portion of its code, is a Licensee as defined above and is subject to the terms 204 | and conditions of this License. 205 | 206 | 6. **REPRESENTATIONS AND WARRANTIES:** 207 | 208 | 6.1. _Disclaimer of Warranty_: TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE COMES "AS IS," WITHOUT ANY WARRANTY, 209 | EXPRESS OR IMPLIED, AND LICENSOR SHALL NOT BE LIABLE TO ANY PERSON OR ENTITY FOR ANY DAMAGES OR OTHER LIABILITY 210 | ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY LEGAL CLAIM. 211 | 212 | 6.2. _Limitation of Liability_: LICENSEE SHALL HOLD LICENSOR HARMLESS AGAINST ANY AND ALL CLAIMS, DEBTS, DUES, 213 | LIABILITIES, LIENS, CAUSES OF ACTION, DEMANDS, OBLIGATIONS, DISPUTES, DAMAGES, LOSSES, EXPENSES, ATTORNEYS' FEES, 214 | COSTS, LIABILITIES, AND ALL OTHER CLAIMS OF EVERY KIND AND NATURE WHATSOEVER, WHETHER KNOWN OR UNKNOWN, ANTICIPATED 215 | OR UNANTICIPATED, FORESEEN OR UNFORESEEN, ACCRUED OR UNACCRUED, DISCLOSED OR UNDISCLOSED, ARISING OUT OF OR RELATING 216 | TO LICENSEE'S USE OF THE SOFTWARE. NOTHING IN THIS SECTION SHOULD BE INTERPRETED TO REQUIRE LICENSEE TO INDEMNIFY 217 | LICENSOR, NOR REQUIRE LICENSOR TO INDEMNIFY LICENSEE. 218 | 219 | 7. **TERMINATION** 220 | 221 | 7.1. _Violations of Ethical Standards or Breaching Duty of Care_: If Licensee violates the Ethical Standards section 222 | or Licensee, or any other person or entity within the Supply Chain prior to a good or service reaching the Licensee, 223 | breaches its Duty of Care to Supply Chain Impacted Parties, Licensee must remedy the violation or harm caused by 224 | Licensee within 30 days of being notified of the violation or harm. If Licensee fails to remedy the violation or 225 | harm within 30 days, all rights in the Software granted to Licensee by License will be null and void as between 226 | Licensor and Licensee. 227 | 228 | 7.2. _Failure of Notice_: If any person or entity notifies Licensee in writing that Licensee has not complied with 229 | the Notice section of this License, Licensee can keep this License by taking all practical steps to comply within 30 230 | days after the notice of noncompliance. If Licensee does not do so, Licensee's License (and all rights licensed 231 | hereunder) will end immediately. 232 | 233 | 7.3. _Judicial Findings_: In the event Licensee is found by a civil, criminal, administrative, or other court of 234 | competent jurisdiction, or some other adjudicating body with legal authority, to have committed actions which are in 235 | violation of the Ethical Standards or Supply Chain Impacted Party sections of this License, all rights granted to 236 | Licensee by this License will terminate immediately. 237 | 238 | 7.4. _Patent Litigation_: If Licensee institutes patent litigation against any entity (including a cross-claim or 239 | counterclaim in a suit) alleging that the Software, all or part of the Software's code, or a derivative work 240 | developed using the Software, including a portion of its code, constitutes direct or contributory patent 241 | infringement, then any patent license, along with all other rights, granted to Licensee under this License will 242 | terminate as of the date such litigation is filed. 243 | 244 | 7.5. _Additional Remedies_: Termination of the License by failing to remedy harms in no way prevents Licensor or 245 | Supply Chain Impacted Party from seeking appropriate remedies at law or in equity. 246 | 247 | 8. **MISCELLANEOUS:** 248 | 249 | 8.1. _Conditions_: Sections 3, 4.1, 5.1, 5.2, 7.1, 7.2, 7.3, and 7.4 are conditions of the rights granted to 250 | Licensee in the License. 251 | 252 | 8.2. _Equitable Relief_: Licensor and any Supply Chain Impacted Party shall be entitled to equitable relief, 253 | including injunctive relief or specific performance of the terms hereof, in addition to any other remedy to which 254 | they are entitled at law or in equity. 255 | 256 | 8.3. (Module -- Copyleft) _Copyleft_: Modified software, source code, or other derivative work must be licensed, in 257 | its entirety, under the exact same conditions as this License. 258 | 259 | 8.4. _Severability_: If any term or provision of this License is determined to be invalid, illegal, or unenforceable 260 | by a court of competent jurisdiction, any such determination of invalidity, illegality, or unenforceability shall 261 | not affect any other term or provision of this License or invalidate or render unenforceable such term or provision 262 | in any other jurisdiction. If the determination of invalidity, illegality, or unenforceability by a court of 263 | competent jurisdiction pertains to the terms or provisions contained in the Ethical Standards section of this 264 | License, all rights in the Software granted to Licensee shall be deemed null and void as between Licensor and 265 | Licensee. 266 | 267 | 8.5. _Section Titles_: Section titles are solely written for organizational purposes and should not be used to 268 | interpret the language within each section. 269 | 270 | 8.6. _Citations_: Citations are solely written to provide context for the source of the provisions in the Ethical 271 | Standards. 272 | 273 | 8.7. _Section Summaries_: Some sections have a brief _italicized description_ which is provided for the sole purpose 274 | of briefly describing the section and should not be used to interpret the terms of the License. 275 | 276 | 8.8. _Entire License_: This is the entire License between the Licensor and Licensee with respect to the claims 277 | released herein and that the consideration stated herein is the only consideration or compensation to be paid or 278 | exchanged between them for this License. This License cannot be modified or amended except in a writing signed by 279 | Licensor and Licensee. 280 | 281 | 8.9. _Successors and Assigns_: This License shall be binding upon and inure to the benefit of the Licensor's and 282 | Licensee's respective heirs, successors, and assigns. 283 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `prisma-redis-middleware` 2 | 3 | [![License: Hippocratic 3.0](https://img.shields.io/badge/License-Hippocratic_3.0-lightgrey.svg)](https://firstdonoharm.dev) 4 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 5 | [![npm version](https://badge.fury.io/js/prisma-redis-middleware.svg)](https://badge.fury.io/js/prisma-redis-middleware) 6 | [![codecov](https://codecov.io/gh/Asjas/prisma-redis-middleware/branch/main/graph/badge.svg?token=6F6DDOSRK8)](https://codecov.io/gh/Asjas/prisma-redis-middleware) 7 | [![Main WorkFlow](https://github.com/Asjas/prisma-redis-middleware/actions/workflows/main.yml/badge.svg)](https://github.com/Asjas/prisma-redis-middleware/actions/workflows/main.yml) 8 | [![CodeQL WorkFlow](https://github.com/Asjas/prisma-redis-middleware/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/Asjas/prisma-redis-middleware/actions/workflows/codeql-analysis.yml) 9 | [![Library code size](https://github.com/Asjas/prisma-redis-middleware/actions/workflows/size.yml/badge.svg)](https://github.com/Asjas/prisma-redis-middleware/actions/workflows/size.yml) 10 | 11 | This is a Prisma middleware used for caching and storing of Prisma queries in Redis (uses an in-memory LRU cache as 12 | fallback storage). 13 | 14 | Uses [async-cache-dedupe](https://github.com/mcollina/async-cache-dedupe). 15 | 16 | ## Features 17 | 18 | - Cache Invalidation 19 | - Supports custom cache keys 20 | - Cache persistance with Redis (uses an in-memory LRU cache as fallback) 21 | - Caching multiple Prisma models each with a specific cache time 22 | - Excluding certain Prisma models from being cached 23 | - Excluding certain Prisma queries from being cached across all models 24 | 25 | ## Supported Node.js versions 26 | 27 | The latest versions of the following Node.js versions are tested and supported. 28 | 29 | - 16 30 | - 18 31 | 32 | ## Default Cached Methods 33 | 34 | Here is a list of all the Prisma methods that are currently cached by default in `prisma-redis-middleware`. 35 | 36 | - findUnique 37 | - findUniqueOrThrow 38 | - findFirst 39 | - findFirstOrThrow 40 | - findMany 41 | - count 42 | - aggregate 43 | - groupBy 44 | - findRaw 45 | - aggregateRaw 46 | 47 | `queryRaw` is not cached as it's executed against the Prisma db itself and not a model. This Prisma middleware is used 48 | for caching queries based on the models that they are executed against. 49 | 50 | ## Quick Start 51 | 52 | Install the package using `npm`: 53 | 54 | ```sh 55 | npm i --save-exact prisma-redis-middleware 56 | ``` 57 | 58 | _You will also need to install and configure an external dependency for `Redis` (for example: `ioredis` or one that uses 59 | a similar API) if you don't already have a Redis Client in your project._ 60 | 61 | ```sh 62 | npm i --save-exact ioredis @types/ioredis 63 | ``` 64 | 65 | ## Code Example (ESM / Import) 66 | 67 | ```mjs 68 | import Prisma from "prisma"; 69 | import { PrismaClient } from "@prisma/client"; 70 | import { createPrismaRedisCache } from "prisma-redis-middleware"; 71 | import Redis from "ioredis"; 72 | 73 | const redis = new Redis(); // Uses default options for Redis connection 74 | 75 | const prisma = new PrismaClient(); 76 | 77 | const cacheMiddleware: Prisma.Middleware = createPrismaRedisCache({ 78 | models: [ 79 | { model: "User", excludeMethods: ["findMany"] }, 80 | { model: "Post", cacheTime: 180, cacheKey: "article" }, 81 | ], 82 | storage: { type: "redis", options: { client: redis, invalidation: { referencesTTL: 300 }, log: console } }, 83 | cacheTime: 300, 84 | excludeModels: ["Product", "Cart"], 85 | excludeMethods: ["count", "groupBy"], 86 | onHit: (key) => { 87 | console.log("hit", key); 88 | }, 89 | onMiss: (key) => { 90 | console.log("miss", key); 91 | }, 92 | onError: (key) => { 93 | console.log("error", key); 94 | }, 95 | }); 96 | 97 | prisma.$use(cacheMiddleware); 98 | ``` 99 | 100 | ## Code Example (Common JS / Require) 101 | 102 | ```js 103 | const Prisma = require("prisma"); 104 | const { PrismaClient } = require("@prisma/client"); 105 | const { createPrismaRedisCache } = require("prisma-redis-middleware"); 106 | 107 | const prisma = new PrismaClient(); 108 | 109 | const cacheMiddleware: Prisma.Middleware = createPrismaRedisCache({ 110 | models: [ 111 | { model: "User", cacheTime: 60 }, 112 | { model: "Post", cacheTime: 180 }, 113 | ], 114 | storage: { type: "memory", options: { invalidation: true, log: console } }, 115 | cacheTime: 300, 116 | onHit: (key) => { 117 | console.log("hit", key); 118 | }, 119 | onMiss: (key) => { 120 | console.log("miss", key); 121 | }, 122 | onError: (key) => { 123 | console.log("error", key); 124 | }, 125 | }); 126 | 127 | prisma.$use(cacheMiddleware); 128 | ``` 129 | 130 | ## API 131 | 132 | ### `createPrismaRedisCache(opts)` 133 | 134 | Options: 135 | 136 | - `onDedupe`: (optional) a function that is called every time a query is deduped. 137 | - `onError`: (optional) a function that is called every time there is a cache error. 138 | - `onHit`: (optional) a function that is called every time there is a hit in the cache. 139 | - `onMiss`: (optional) a function that is called every time the result is not in the cache. 140 | - `cacheTime`: (optional) (number) the default time (in seconds) to use for models that don't have a `cacheTime` value set. 141 | Default is 0. 142 | - `excludeModels`: (optional) (string) an array of models to exclude from being cached. 143 | - `excludeMethods`: (optional) (string) an array of Prisma methods to exclude from being cached for all models. 144 | - `models`: (optional) an array of Prisma models. Models options are: 145 | 146 | - `model`: (required) string. 147 | - `cacheKey`: (optional) string. Default is the model value. 148 | - `cacheTime`: (optional) number (in seconds). 149 | - `excludeMethods`: (optional) (string) an array of Prisma methods to exclude from being cached for this model. 150 | - `invalidateRelated`: (optional) (string) an array of Prisma models to invalidate when mutating this model. 151 | 152 | Example: 153 | 154 | ```js 155 | createPrismaRedisCache({ 156 | models: [ 157 | { model: "User", cacheTime: 60, invalidateRelated: ["Post"] }, 158 | { model: "Post", cacheKey: "article", excludeMethods: ["findFirst"] }, 159 | ], 160 | }); 161 | ``` 162 | 163 | - `storage`: (optional) the storage options; default is `{ type: "memory" }`. Storage options are: 164 | 165 | - `type`: `memory` (default) or `redis` 166 | - `options`: by storage type 167 | 168 | - for `memory` type 169 | 170 | - `size`: (optional) maximum number of items to store in the cache. Default is `1024`. 171 | - `invalidation`: (optional) enable invalidation. Default is disabled. 172 | - `log`: (optional) logger instance `pino` compatible, or `console`, default is disabled. 173 | 174 | Example: 175 | 176 | ```js 177 | createPrismaRedisCache({ 178 | storage: { type: "memory", options: { size: 2048 }, log: console }, 179 | }); 180 | ``` 181 | 182 | - for `redis` type 183 | 184 | - `client`: a redis client instance, mandatory. Should be an `ioredis` client or compatible. 185 | - `invalidation`: (optional) enable invalidation. Default is disabled. 186 | - `invalidation.referencesTTL`: (optional) references TTL in seconds, it means how long the references are alive; 187 | it should be set at the maximum of all the caches ttl. 188 | - `log`: (optional) logger instance `pino` compatible, or `console`, default is disabled. 189 | 190 | Example 191 | 192 | ```js 193 | const redis = new Redis(); 194 | 195 | createPrismaRedisCache({ 196 | storage: { 197 | type: "redis", 198 | options: { client: redis, invalidation: { referencesTTL: 60 }, log: console }, 199 | }, 200 | }); 201 | ``` 202 | 203 | - `transformer`: (optional) the transformer to used to serialize and deserialize the cache entries. It must be an object 204 | with the following methods: 205 | 206 | - `serialize`: a function that receives the result of the original function and returns a serializable object. 207 | - `deserialize`: a function that receives the serialized object and returns the original result. 208 | 209 | - Default is `undefined`, so the default transformer is used. 210 | 211 | Example 212 | 213 | ```js 214 | import superjson from "superjson"; 215 | 216 | createPrismaRedisCache({ 217 | transformer: { 218 | serialize: (result) => superjson.serialize(result), 219 | deserialize: (serialized) => superjson.deserialize(serialized), 220 | }, 221 | }); 222 | ``` 223 | 224 | ## Debugging 225 | 226 | You can pass functions for `onMiss`, `onHit`, `onError` and `onDedupe` to `createPrismaRedisCache` which can then be 227 | used to debug whether a Prisma query is being cached or not. 228 | 229 | You can also pass a custom `log` (pino or console) to the `storage` option and `async-cache-dedupe` will print debug 230 | info as it queries, sets, expires and invalidates the cache. Note that the `log` option can print out very verbose 231 | output. 232 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 3.x.x | :white_check_mark: | 8 | | 2.x.x | :heavy_multiplication_x: | 9 | | 1.x.x | :heavy_multiplication_x: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Any security vulnerabilities can be reported via email to asjas@hey.com. 14 | -------------------------------------------------------------------------------- /example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint-config-asjas/node-typescript"], 3 | "parserOptions": { 4 | "tsconfigRootDir": ".", 5 | "project": "tsconfig.json" 6 | }, 7 | "rules": { 8 | "no-shadow": 0, 9 | "no-console": 0, 10 | "node/no-unpublished-require": 0, 11 | "testing-library/no-debug": 0, 12 | "@typescript-eslint/no-shadow": 0, 13 | "@typescript-eslint/no-var-requires": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example App 2 | -------------------------------------------------------------------------------- /example/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts", 4 | "ignore": [], 5 | "exec": "node --inspect=0.0.0.0:9200 --require ts-node/register ./src/index.ts" 6 | } 7 | -------------------------------------------------------------------------------- /example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prisma-redis-middleware", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "prisma-redis-middleware", 8 | "license": "Hippocratic-3.0", 9 | "dependencies": { 10 | "ioredis": "5.3.2", 11 | "pino": "8.16.2", 12 | "prisma": "5.6.0", 13 | "prisma-redis-middleware": "4.8.0" 14 | }, 15 | "devDependencies": { 16 | "@prisma/client": "5.6.0", 17 | "@types/ioredis": "4.28.10", 18 | "@types/node": "20.10.3", 19 | "nodemon": "3.0.2", 20 | "rimraf": "5.0.5", 21 | "ts-node": "10.9.1", 22 | "typescript": "5.3.2" 23 | }, 24 | "engines": { 25 | "node": "^20.0.0", 26 | "npm": "^10.0.0" 27 | } 28 | }, 29 | "node_modules/@cspotcode/source-map-support": { 30 | "version": "0.8.1", 31 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 32 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 33 | "dev": true, 34 | "dependencies": { 35 | "@jridgewell/trace-mapping": "0.3.9" 36 | }, 37 | "engines": { 38 | "node": ">=12" 39 | } 40 | }, 41 | "node_modules/@ioredis/commands": { 42 | "version": "1.2.0", 43 | "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", 44 | "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" 45 | }, 46 | "node_modules/@isaacs/cliui": { 47 | "version": "8.0.2", 48 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 49 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 50 | "dev": true, 51 | "dependencies": { 52 | "string-width": "^5.1.2", 53 | "string-width-cjs": "npm:string-width@^4.2.0", 54 | "strip-ansi": "^7.0.1", 55 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 56 | "wrap-ansi": "^8.1.0", 57 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 58 | }, 59 | "engines": { 60 | "node": ">=12" 61 | } 62 | }, 63 | "node_modules/@jridgewell/resolve-uri": { 64 | "version": "3.1.1", 65 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 66 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 67 | "dev": true, 68 | "engines": { 69 | "node": ">=6.0.0" 70 | } 71 | }, 72 | "node_modules/@jridgewell/sourcemap-codec": { 73 | "version": "1.4.15", 74 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 75 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 76 | "dev": true 77 | }, 78 | "node_modules/@jridgewell/trace-mapping": { 79 | "version": "0.3.9", 80 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 81 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 82 | "dev": true, 83 | "dependencies": { 84 | "@jridgewell/resolve-uri": "^3.0.3", 85 | "@jridgewell/sourcemap-codec": "^1.4.10" 86 | } 87 | }, 88 | "node_modules/@pkgjs/parseargs": { 89 | "version": "0.11.0", 90 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 91 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 92 | "dev": true, 93 | "optional": true, 94 | "engines": { 95 | "node": ">=14" 96 | } 97 | }, 98 | "node_modules/@prisma/client": { 99 | "version": "5.6.0", 100 | "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.6.0.tgz", 101 | "integrity": "sha512-mUDefQFa1wWqk4+JhKPYq8BdVoFk9NFMBXUI8jAkBfQTtgx8WPx02U2HB/XbAz3GSUJpeJOKJQtNvaAIDs6sug==", 102 | "dev": true, 103 | "hasInstallScript": true, 104 | "dependencies": { 105 | "@prisma/engines-version": "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee" 106 | }, 107 | "engines": { 108 | "node": ">=16.13" 109 | }, 110 | "peerDependencies": { 111 | "prisma": "*" 112 | }, 113 | "peerDependenciesMeta": { 114 | "prisma": { 115 | "optional": true 116 | } 117 | } 118 | }, 119 | "node_modules/@prisma/engines": { 120 | "version": "5.6.0", 121 | "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.6.0.tgz", 122 | "integrity": "sha512-Mt2q+GNJpU2vFn6kif24oRSBQv1KOkYaterQsi0k2/lA+dLvhRX6Lm26gon6PYHwUM8/h8KRgXIUMU0PCLB6bw==", 123 | "hasInstallScript": true 124 | }, 125 | "node_modules/@prisma/engines-version": { 126 | "version": "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee", 127 | "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee.tgz", 128 | "integrity": "sha512-UoFgbV1awGL/3wXuUK3GDaX2SolqczeeJ5b4FVec9tzeGbSWJboPSbT0psSrmgYAKiKnkOPFSLlH6+b+IyOwAw==", 129 | "dev": true 130 | }, 131 | "node_modules/@tsconfig/node10": { 132 | "version": "1.0.9", 133 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 134 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 135 | "dev": true 136 | }, 137 | "node_modules/@tsconfig/node12": { 138 | "version": "1.0.11", 139 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 140 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 141 | "dev": true 142 | }, 143 | "node_modules/@tsconfig/node14": { 144 | "version": "1.0.3", 145 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 146 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 147 | "dev": true 148 | }, 149 | "node_modules/@tsconfig/node16": { 150 | "version": "1.0.4", 151 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 152 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 153 | "dev": true 154 | }, 155 | "node_modules/@types/ioredis": { 156 | "version": "4.28.10", 157 | "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.28.10.tgz", 158 | "integrity": "sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==", 159 | "dev": true, 160 | "dependencies": { 161 | "@types/node": "*" 162 | } 163 | }, 164 | "node_modules/@types/node": { 165 | "version": "20.10.3", 166 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", 167 | "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", 168 | "dev": true, 169 | "dependencies": { 170 | "undici-types": "~5.26.4" 171 | } 172 | }, 173 | "node_modules/abbrev": { 174 | "version": "1.1.1", 175 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 176 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 177 | "dev": true 178 | }, 179 | "node_modules/abort-controller": { 180 | "version": "3.0.0", 181 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 182 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 183 | "dependencies": { 184 | "event-target-shim": "^5.0.0" 185 | }, 186 | "engines": { 187 | "node": ">=6.5" 188 | } 189 | }, 190 | "node_modules/acorn": { 191 | "version": "8.11.2", 192 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", 193 | "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", 194 | "dev": true, 195 | "bin": { 196 | "acorn": "bin/acorn" 197 | }, 198 | "engines": { 199 | "node": ">=0.4.0" 200 | } 201 | }, 202 | "node_modules/acorn-walk": { 203 | "version": "8.3.0", 204 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", 205 | "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", 206 | "dev": true, 207 | "engines": { 208 | "node": ">=0.4.0" 209 | } 210 | }, 211 | "node_modules/ansi-regex": { 212 | "version": "6.0.1", 213 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 214 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 215 | "dev": true, 216 | "engines": { 217 | "node": ">=12" 218 | }, 219 | "funding": { 220 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 221 | } 222 | }, 223 | "node_modules/ansi-styles": { 224 | "version": "6.2.1", 225 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 226 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 227 | "dev": true, 228 | "engines": { 229 | "node": ">=12" 230 | }, 231 | "funding": { 232 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 233 | } 234 | }, 235 | "node_modules/anymatch": { 236 | "version": "3.1.3", 237 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 238 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 239 | "dev": true, 240 | "dependencies": { 241 | "normalize-path": "^3.0.0", 242 | "picomatch": "^2.0.4" 243 | }, 244 | "engines": { 245 | "node": ">= 8" 246 | } 247 | }, 248 | "node_modules/arg": { 249 | "version": "4.1.3", 250 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 251 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 252 | "dev": true 253 | }, 254 | "node_modules/async-cache-dedupe": { 255 | "version": "1.12.0", 256 | "resolved": "https://registry.npmjs.org/async-cache-dedupe/-/async-cache-dedupe-1.12.0.tgz", 257 | "integrity": "sha512-LKumaBNhzvZrbrRi3DtIf0ETgjqcGOa/M2U+0DpTp8f+RzIiWubiQmBjuYUaihxetR3cWWC6hQj/uDVBuYajRA==", 258 | "dependencies": { 259 | "mnemonist": "^0.39.2", 260 | "safe-stable-stringify": "^2.3.1" 261 | } 262 | }, 263 | "node_modules/atomic-sleep": { 264 | "version": "1.0.0", 265 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 266 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", 267 | "engines": { 268 | "node": ">=8.0.0" 269 | } 270 | }, 271 | "node_modules/balanced-match": { 272 | "version": "1.0.2", 273 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 274 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 275 | "dev": true 276 | }, 277 | "node_modules/base64-js": { 278 | "version": "1.5.1", 279 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 280 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 281 | "funding": [ 282 | { 283 | "type": "github", 284 | "url": "https://github.com/sponsors/feross" 285 | }, 286 | { 287 | "type": "patreon", 288 | "url": "https://www.patreon.com/feross" 289 | }, 290 | { 291 | "type": "consulting", 292 | "url": "https://feross.org/support" 293 | } 294 | ] 295 | }, 296 | "node_modules/binary-extensions": { 297 | "version": "2.2.0", 298 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 299 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 300 | "dev": true, 301 | "engines": { 302 | "node": ">=8" 303 | } 304 | }, 305 | "node_modules/brace-expansion": { 306 | "version": "1.1.11", 307 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 308 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 309 | "dev": true, 310 | "dependencies": { 311 | "balanced-match": "^1.0.0", 312 | "concat-map": "0.0.1" 313 | } 314 | }, 315 | "node_modules/braces": { 316 | "version": "3.0.2", 317 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 318 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 319 | "dev": true, 320 | "dependencies": { 321 | "fill-range": "^7.0.1" 322 | }, 323 | "engines": { 324 | "node": ">=8" 325 | } 326 | }, 327 | "node_modules/buffer": { 328 | "version": "6.0.3", 329 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", 330 | "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 331 | "funding": [ 332 | { 333 | "type": "github", 334 | "url": "https://github.com/sponsors/feross" 335 | }, 336 | { 337 | "type": "patreon", 338 | "url": "https://www.patreon.com/feross" 339 | }, 340 | { 341 | "type": "consulting", 342 | "url": "https://feross.org/support" 343 | } 344 | ], 345 | "dependencies": { 346 | "base64-js": "^1.3.1", 347 | "ieee754": "^1.2.1" 348 | } 349 | }, 350 | "node_modules/chokidar": { 351 | "version": "3.5.3", 352 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 353 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 354 | "dev": true, 355 | "funding": [ 356 | { 357 | "type": "individual", 358 | "url": "https://paulmillr.com/funding/" 359 | } 360 | ], 361 | "dependencies": { 362 | "anymatch": "~3.1.2", 363 | "braces": "~3.0.2", 364 | "glob-parent": "~5.1.2", 365 | "is-binary-path": "~2.1.0", 366 | "is-glob": "~4.0.1", 367 | "normalize-path": "~3.0.0", 368 | "readdirp": "~3.6.0" 369 | }, 370 | "engines": { 371 | "node": ">= 8.10.0" 372 | }, 373 | "optionalDependencies": { 374 | "fsevents": "~2.3.2" 375 | } 376 | }, 377 | "node_modules/cluster-key-slot": { 378 | "version": "1.1.2", 379 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", 380 | "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", 381 | "engines": { 382 | "node": ">=0.10.0" 383 | } 384 | }, 385 | "node_modules/color-convert": { 386 | "version": "2.0.1", 387 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 388 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 389 | "dev": true, 390 | "dependencies": { 391 | "color-name": "~1.1.4" 392 | }, 393 | "engines": { 394 | "node": ">=7.0.0" 395 | } 396 | }, 397 | "node_modules/color-name": { 398 | "version": "1.1.4", 399 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 400 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 401 | "dev": true 402 | }, 403 | "node_modules/concat-map": { 404 | "version": "0.0.1", 405 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 406 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 407 | "dev": true 408 | }, 409 | "node_modules/create-require": { 410 | "version": "1.1.1", 411 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 412 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 413 | "dev": true 414 | }, 415 | "node_modules/cross-spawn": { 416 | "version": "7.0.3", 417 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 418 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 419 | "dev": true, 420 | "dependencies": { 421 | "path-key": "^3.1.0", 422 | "shebang-command": "^2.0.0", 423 | "which": "^2.0.1" 424 | }, 425 | "engines": { 426 | "node": ">= 8" 427 | } 428 | }, 429 | "node_modules/debug": { 430 | "version": "4.3.4", 431 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 432 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 433 | "dependencies": { 434 | "ms": "2.1.2" 435 | }, 436 | "engines": { 437 | "node": ">=6.0" 438 | }, 439 | "peerDependenciesMeta": { 440 | "supports-color": { 441 | "optional": true 442 | } 443 | } 444 | }, 445 | "node_modules/denque": { 446 | "version": "2.1.0", 447 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", 448 | "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", 449 | "engines": { 450 | "node": ">=0.10" 451 | } 452 | }, 453 | "node_modules/diff": { 454 | "version": "4.0.2", 455 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 456 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 457 | "dev": true, 458 | "engines": { 459 | "node": ">=0.3.1" 460 | } 461 | }, 462 | "node_modules/eastasianwidth": { 463 | "version": "0.2.0", 464 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 465 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 466 | "dev": true 467 | }, 468 | "node_modules/emoji-regex": { 469 | "version": "9.2.2", 470 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 471 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 472 | "dev": true 473 | }, 474 | "node_modules/event-target-shim": { 475 | "version": "5.0.1", 476 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 477 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 478 | "engines": { 479 | "node": ">=6" 480 | } 481 | }, 482 | "node_modules/events": { 483 | "version": "3.3.0", 484 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 485 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 486 | "engines": { 487 | "node": ">=0.8.x" 488 | } 489 | }, 490 | "node_modules/fast-redact": { 491 | "version": "3.3.0", 492 | "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", 493 | "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", 494 | "engines": { 495 | "node": ">=6" 496 | } 497 | }, 498 | "node_modules/fill-range": { 499 | "version": "7.0.1", 500 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 501 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 502 | "dev": true, 503 | "dependencies": { 504 | "to-regex-range": "^5.0.1" 505 | }, 506 | "engines": { 507 | "node": ">=8" 508 | } 509 | }, 510 | "node_modules/foreground-child": { 511 | "version": "3.1.1", 512 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", 513 | "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", 514 | "dev": true, 515 | "dependencies": { 516 | "cross-spawn": "^7.0.0", 517 | "signal-exit": "^4.0.1" 518 | }, 519 | "engines": { 520 | "node": ">=14" 521 | }, 522 | "funding": { 523 | "url": "https://github.com/sponsors/isaacs" 524 | } 525 | }, 526 | "node_modules/fsevents": { 527 | "version": "2.3.3", 528 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 529 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 530 | "dev": true, 531 | "hasInstallScript": true, 532 | "optional": true, 533 | "os": [ 534 | "darwin" 535 | ], 536 | "engines": { 537 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 538 | } 539 | }, 540 | "node_modules/glob": { 541 | "version": "10.3.10", 542 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", 543 | "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", 544 | "dev": true, 545 | "dependencies": { 546 | "foreground-child": "^3.1.0", 547 | "jackspeak": "^2.3.5", 548 | "minimatch": "^9.0.1", 549 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", 550 | "path-scurry": "^1.10.1" 551 | }, 552 | "bin": { 553 | "glob": "dist/esm/bin.mjs" 554 | }, 555 | "engines": { 556 | "node": ">=16 || 14 >=14.17" 557 | }, 558 | "funding": { 559 | "url": "https://github.com/sponsors/isaacs" 560 | } 561 | }, 562 | "node_modules/glob-parent": { 563 | "version": "5.1.2", 564 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 565 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 566 | "dev": true, 567 | "dependencies": { 568 | "is-glob": "^4.0.1" 569 | }, 570 | "engines": { 571 | "node": ">= 6" 572 | } 573 | }, 574 | "node_modules/glob/node_modules/brace-expansion": { 575 | "version": "2.0.1", 576 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 577 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 578 | "dev": true, 579 | "dependencies": { 580 | "balanced-match": "^1.0.0" 581 | } 582 | }, 583 | "node_modules/glob/node_modules/minimatch": { 584 | "version": "9.0.3", 585 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", 586 | "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", 587 | "dev": true, 588 | "dependencies": { 589 | "brace-expansion": "^2.0.1" 590 | }, 591 | "engines": { 592 | "node": ">=16 || 14 >=14.17" 593 | }, 594 | "funding": { 595 | "url": "https://github.com/sponsors/isaacs" 596 | } 597 | }, 598 | "node_modules/has-flag": { 599 | "version": "3.0.0", 600 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 601 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 602 | "dev": true, 603 | "engines": { 604 | "node": ">=4" 605 | } 606 | }, 607 | "node_modules/ieee754": { 608 | "version": "1.2.1", 609 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 610 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 611 | "funding": [ 612 | { 613 | "type": "github", 614 | "url": "https://github.com/sponsors/feross" 615 | }, 616 | { 617 | "type": "patreon", 618 | "url": "https://www.patreon.com/feross" 619 | }, 620 | { 621 | "type": "consulting", 622 | "url": "https://feross.org/support" 623 | } 624 | ] 625 | }, 626 | "node_modules/ignore-by-default": { 627 | "version": "1.0.1", 628 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 629 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 630 | "dev": true 631 | }, 632 | "node_modules/ioredis": { 633 | "version": "5.3.2", 634 | "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", 635 | "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==", 636 | "dependencies": { 637 | "@ioredis/commands": "^1.1.1", 638 | "cluster-key-slot": "^1.1.0", 639 | "debug": "^4.3.4", 640 | "denque": "^2.1.0", 641 | "lodash.defaults": "^4.2.0", 642 | "lodash.isarguments": "^3.1.0", 643 | "redis-errors": "^1.2.0", 644 | "redis-parser": "^3.0.0", 645 | "standard-as-callback": "^2.1.0" 646 | }, 647 | "engines": { 648 | "node": ">=12.22.0" 649 | }, 650 | "funding": { 651 | "type": "opencollective", 652 | "url": "https://opencollective.com/ioredis" 653 | } 654 | }, 655 | "node_modules/is-binary-path": { 656 | "version": "2.1.0", 657 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 658 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 659 | "dev": true, 660 | "dependencies": { 661 | "binary-extensions": "^2.0.0" 662 | }, 663 | "engines": { 664 | "node": ">=8" 665 | } 666 | }, 667 | "node_modules/is-extglob": { 668 | "version": "2.1.1", 669 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 670 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 671 | "dev": true, 672 | "engines": { 673 | "node": ">=0.10.0" 674 | } 675 | }, 676 | "node_modules/is-fullwidth-code-point": { 677 | "version": "3.0.0", 678 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 679 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 680 | "dev": true, 681 | "engines": { 682 | "node": ">=8" 683 | } 684 | }, 685 | "node_modules/is-glob": { 686 | "version": "4.0.3", 687 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 688 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 689 | "dev": true, 690 | "dependencies": { 691 | "is-extglob": "^2.1.1" 692 | }, 693 | "engines": { 694 | "node": ">=0.10.0" 695 | } 696 | }, 697 | "node_modules/is-number": { 698 | "version": "7.0.0", 699 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 700 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 701 | "dev": true, 702 | "engines": { 703 | "node": ">=0.12.0" 704 | } 705 | }, 706 | "node_modules/isexe": { 707 | "version": "2.0.0", 708 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 709 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 710 | "dev": true 711 | }, 712 | "node_modules/jackspeak": { 713 | "version": "2.3.6", 714 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", 715 | "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", 716 | "dev": true, 717 | "dependencies": { 718 | "@isaacs/cliui": "^8.0.2" 719 | }, 720 | "engines": { 721 | "node": ">=14" 722 | }, 723 | "funding": { 724 | "url": "https://github.com/sponsors/isaacs" 725 | }, 726 | "optionalDependencies": { 727 | "@pkgjs/parseargs": "^0.11.0" 728 | } 729 | }, 730 | "node_modules/lodash.defaults": { 731 | "version": "4.2.0", 732 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 733 | "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" 734 | }, 735 | "node_modules/lodash.isarguments": { 736 | "version": "3.1.0", 737 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 738 | "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" 739 | }, 740 | "node_modules/lru-cache": { 741 | "version": "10.1.0", 742 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", 743 | "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", 744 | "dev": true, 745 | "engines": { 746 | "node": "14 || >=16.14" 747 | } 748 | }, 749 | "node_modules/make-error": { 750 | "version": "1.3.6", 751 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 752 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 753 | "dev": true 754 | }, 755 | "node_modules/minimatch": { 756 | "version": "3.1.2", 757 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 758 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 759 | "dev": true, 760 | "dependencies": { 761 | "brace-expansion": "^1.1.7" 762 | }, 763 | "engines": { 764 | "node": "*" 765 | } 766 | }, 767 | "node_modules/minipass": { 768 | "version": "7.0.4", 769 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", 770 | "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", 771 | "dev": true, 772 | "engines": { 773 | "node": ">=16 || 14 >=14.17" 774 | } 775 | }, 776 | "node_modules/mnemonist": { 777 | "version": "0.39.5", 778 | "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", 779 | "integrity": "sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==", 780 | "dependencies": { 781 | "obliterator": "^2.0.1" 782 | } 783 | }, 784 | "node_modules/ms": { 785 | "version": "2.1.2", 786 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 787 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 788 | }, 789 | "node_modules/nodemon": { 790 | "version": "3.0.2", 791 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.2.tgz", 792 | "integrity": "sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA==", 793 | "dev": true, 794 | "dependencies": { 795 | "chokidar": "^3.5.2", 796 | "debug": "^4", 797 | "ignore-by-default": "^1.0.1", 798 | "minimatch": "^3.1.2", 799 | "pstree.remy": "^1.1.8", 800 | "semver": "^7.5.3", 801 | "simple-update-notifier": "^2.0.0", 802 | "supports-color": "^5.5.0", 803 | "touch": "^3.1.0", 804 | "undefsafe": "^2.0.5" 805 | }, 806 | "bin": { 807 | "nodemon": "bin/nodemon.js" 808 | }, 809 | "engines": { 810 | "node": ">=10" 811 | }, 812 | "funding": { 813 | "type": "opencollective", 814 | "url": "https://opencollective.com/nodemon" 815 | } 816 | }, 817 | "node_modules/nopt": { 818 | "version": "1.0.10", 819 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 820 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 821 | "dev": true, 822 | "dependencies": { 823 | "abbrev": "1" 824 | }, 825 | "bin": { 826 | "nopt": "bin/nopt.js" 827 | }, 828 | "engines": { 829 | "node": "*" 830 | } 831 | }, 832 | "node_modules/normalize-path": { 833 | "version": "3.0.0", 834 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 835 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 836 | "dev": true, 837 | "engines": { 838 | "node": ">=0.10.0" 839 | } 840 | }, 841 | "node_modules/obliterator": { 842 | "version": "2.0.4", 843 | "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", 844 | "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" 845 | }, 846 | "node_modules/on-exit-leak-free": { 847 | "version": "2.1.2", 848 | "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", 849 | "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", 850 | "engines": { 851 | "node": ">=14.0.0" 852 | } 853 | }, 854 | "node_modules/path-key": { 855 | "version": "3.1.1", 856 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 857 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 858 | "dev": true, 859 | "engines": { 860 | "node": ">=8" 861 | } 862 | }, 863 | "node_modules/path-scurry": { 864 | "version": "1.10.1", 865 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", 866 | "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", 867 | "dev": true, 868 | "dependencies": { 869 | "lru-cache": "^9.1.1 || ^10.0.0", 870 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 871 | }, 872 | "engines": { 873 | "node": ">=16 || 14 >=14.17" 874 | }, 875 | "funding": { 876 | "url": "https://github.com/sponsors/isaacs" 877 | } 878 | }, 879 | "node_modules/picomatch": { 880 | "version": "2.3.1", 881 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 882 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 883 | "dev": true, 884 | "engines": { 885 | "node": ">=8.6" 886 | }, 887 | "funding": { 888 | "url": "https://github.com/sponsors/jonschlinkert" 889 | } 890 | }, 891 | "node_modules/pino": { 892 | "version": "8.16.2", 893 | "resolved": "https://registry.npmjs.org/pino/-/pino-8.16.2.tgz", 894 | "integrity": "sha512-2advCDGVEvkKu9TTVSa/kWW7Z3htI/sBKEZpqiHk6ive0i/7f5b1rsU8jn0aimxqfnSz5bj/nOYkwhBUn5xxvg==", 895 | "dependencies": { 896 | "atomic-sleep": "^1.0.0", 897 | "fast-redact": "^3.1.1", 898 | "on-exit-leak-free": "^2.1.0", 899 | "pino-abstract-transport": "v1.1.0", 900 | "pino-std-serializers": "^6.0.0", 901 | "process-warning": "^2.0.0", 902 | "quick-format-unescaped": "^4.0.3", 903 | "real-require": "^0.2.0", 904 | "safe-stable-stringify": "^2.3.1", 905 | "sonic-boom": "^3.7.0", 906 | "thread-stream": "^2.0.0" 907 | }, 908 | "bin": { 909 | "pino": "bin.js" 910 | } 911 | }, 912 | "node_modules/pino-abstract-transport": { 913 | "version": "1.1.0", 914 | "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", 915 | "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", 916 | "dependencies": { 917 | "readable-stream": "^4.0.0", 918 | "split2": "^4.0.0" 919 | } 920 | }, 921 | "node_modules/pino-std-serializers": { 922 | "version": "6.2.2", 923 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", 924 | "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" 925 | }, 926 | "node_modules/prisma": { 927 | "version": "5.6.0", 928 | "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.6.0.tgz", 929 | "integrity": "sha512-EEaccku4ZGshdr2cthYHhf7iyvCcXqwJDvnoQRAJg5ge2Tzpv0e2BaMCp+CbbDUwoVTzwgOap9Zp+d4jFa2O9A==", 930 | "hasInstallScript": true, 931 | "dependencies": { 932 | "@prisma/engines": "5.6.0" 933 | }, 934 | "bin": { 935 | "prisma": "build/index.js" 936 | }, 937 | "engines": { 938 | "node": ">=16.13" 939 | } 940 | }, 941 | "node_modules/prisma-redis-middleware": { 942 | "version": "4.8.0", 943 | "resolved": "https://registry.npmjs.org/prisma-redis-middleware/-/prisma-redis-middleware-4.8.0.tgz", 944 | "integrity": "sha512-d1B7TVLiR8aSiOY2GPKKDb5EWGTK+EmlIkztAZCPWxs4/IHGfIAZiDoLklp6CcK+5ckjcaJq8aWE5AjRJ3+Rpg==", 945 | "dependencies": { 946 | "async-cache-dedupe": "1.12.0", 947 | "ioredis": "5.3.2" 948 | }, 949 | "engines": { 950 | "node": "^16.x || ^18.x", 951 | "npm": "^7.x || ^8.x || ^9.x" 952 | } 953 | }, 954 | "node_modules/process": { 955 | "version": "0.11.10", 956 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 957 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 958 | "engines": { 959 | "node": ">= 0.6.0" 960 | } 961 | }, 962 | "node_modules/process-warning": { 963 | "version": "2.3.2", 964 | "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz", 965 | "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==" 966 | }, 967 | "node_modules/pstree.remy": { 968 | "version": "1.1.8", 969 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 970 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 971 | "dev": true 972 | }, 973 | "node_modules/quick-format-unescaped": { 974 | "version": "4.0.4", 975 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", 976 | "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" 977 | }, 978 | "node_modules/readable-stream": { 979 | "version": "4.4.2", 980 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", 981 | "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", 982 | "dependencies": { 983 | "abort-controller": "^3.0.0", 984 | "buffer": "^6.0.3", 985 | "events": "^3.3.0", 986 | "process": "^0.11.10", 987 | "string_decoder": "^1.3.0" 988 | }, 989 | "engines": { 990 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 991 | } 992 | }, 993 | "node_modules/readdirp": { 994 | "version": "3.6.0", 995 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 996 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 997 | "dev": true, 998 | "dependencies": { 999 | "picomatch": "^2.2.1" 1000 | }, 1001 | "engines": { 1002 | "node": ">=8.10.0" 1003 | } 1004 | }, 1005 | "node_modules/real-require": { 1006 | "version": "0.2.0", 1007 | "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", 1008 | "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", 1009 | "engines": { 1010 | "node": ">= 12.13.0" 1011 | } 1012 | }, 1013 | "node_modules/redis-errors": { 1014 | "version": "1.2.0", 1015 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 1016 | "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", 1017 | "engines": { 1018 | "node": ">=4" 1019 | } 1020 | }, 1021 | "node_modules/redis-parser": { 1022 | "version": "3.0.0", 1023 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 1024 | "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", 1025 | "dependencies": { 1026 | "redis-errors": "^1.0.0" 1027 | }, 1028 | "engines": { 1029 | "node": ">=4" 1030 | } 1031 | }, 1032 | "node_modules/rimraf": { 1033 | "version": "5.0.5", 1034 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", 1035 | "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", 1036 | "dev": true, 1037 | "dependencies": { 1038 | "glob": "^10.3.7" 1039 | }, 1040 | "bin": { 1041 | "rimraf": "dist/esm/bin.mjs" 1042 | }, 1043 | "engines": { 1044 | "node": ">=14" 1045 | }, 1046 | "funding": { 1047 | "url": "https://github.com/sponsors/isaacs" 1048 | } 1049 | }, 1050 | "node_modules/safe-buffer": { 1051 | "version": "5.2.1", 1052 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1053 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1054 | "funding": [ 1055 | { 1056 | "type": "github", 1057 | "url": "https://github.com/sponsors/feross" 1058 | }, 1059 | { 1060 | "type": "patreon", 1061 | "url": "https://www.patreon.com/feross" 1062 | }, 1063 | { 1064 | "type": "consulting", 1065 | "url": "https://feross.org/support" 1066 | } 1067 | ] 1068 | }, 1069 | "node_modules/safe-stable-stringify": { 1070 | "version": "2.4.3", 1071 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", 1072 | "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", 1073 | "engines": { 1074 | "node": ">=10" 1075 | } 1076 | }, 1077 | "node_modules/semver": { 1078 | "version": "7.5.4", 1079 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1080 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1081 | "dev": true, 1082 | "dependencies": { 1083 | "lru-cache": "^6.0.0" 1084 | }, 1085 | "bin": { 1086 | "semver": "bin/semver.js" 1087 | }, 1088 | "engines": { 1089 | "node": ">=10" 1090 | } 1091 | }, 1092 | "node_modules/semver/node_modules/lru-cache": { 1093 | "version": "6.0.0", 1094 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1095 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1096 | "dev": true, 1097 | "dependencies": { 1098 | "yallist": "^4.0.0" 1099 | }, 1100 | "engines": { 1101 | "node": ">=10" 1102 | } 1103 | }, 1104 | "node_modules/shebang-command": { 1105 | "version": "2.0.0", 1106 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1107 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1108 | "dev": true, 1109 | "dependencies": { 1110 | "shebang-regex": "^3.0.0" 1111 | }, 1112 | "engines": { 1113 | "node": ">=8" 1114 | } 1115 | }, 1116 | "node_modules/shebang-regex": { 1117 | "version": "3.0.0", 1118 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1119 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1120 | "dev": true, 1121 | "engines": { 1122 | "node": ">=8" 1123 | } 1124 | }, 1125 | "node_modules/signal-exit": { 1126 | "version": "4.1.0", 1127 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1128 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1129 | "dev": true, 1130 | "engines": { 1131 | "node": ">=14" 1132 | }, 1133 | "funding": { 1134 | "url": "https://github.com/sponsors/isaacs" 1135 | } 1136 | }, 1137 | "node_modules/simple-update-notifier": { 1138 | "version": "2.0.0", 1139 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 1140 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 1141 | "dev": true, 1142 | "dependencies": { 1143 | "semver": "^7.5.3" 1144 | }, 1145 | "engines": { 1146 | "node": ">=10" 1147 | } 1148 | }, 1149 | "node_modules/sonic-boom": { 1150 | "version": "3.7.0", 1151 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", 1152 | "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==", 1153 | "dependencies": { 1154 | "atomic-sleep": "^1.0.0" 1155 | } 1156 | }, 1157 | "node_modules/split2": { 1158 | "version": "4.2.0", 1159 | "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", 1160 | "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", 1161 | "engines": { 1162 | "node": ">= 10.x" 1163 | } 1164 | }, 1165 | "node_modules/standard-as-callback": { 1166 | "version": "2.1.0", 1167 | "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", 1168 | "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" 1169 | }, 1170 | "node_modules/string_decoder": { 1171 | "version": "1.3.0", 1172 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1173 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1174 | "dependencies": { 1175 | "safe-buffer": "~5.2.0" 1176 | } 1177 | }, 1178 | "node_modules/string-width": { 1179 | "version": "5.1.2", 1180 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 1181 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 1182 | "dev": true, 1183 | "dependencies": { 1184 | "eastasianwidth": "^0.2.0", 1185 | "emoji-regex": "^9.2.2", 1186 | "strip-ansi": "^7.0.1" 1187 | }, 1188 | "engines": { 1189 | "node": ">=12" 1190 | }, 1191 | "funding": { 1192 | "url": "https://github.com/sponsors/sindresorhus" 1193 | } 1194 | }, 1195 | "node_modules/string-width-cjs": { 1196 | "name": "string-width", 1197 | "version": "4.2.3", 1198 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1199 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1200 | "dev": true, 1201 | "dependencies": { 1202 | "emoji-regex": "^8.0.0", 1203 | "is-fullwidth-code-point": "^3.0.0", 1204 | "strip-ansi": "^6.0.1" 1205 | }, 1206 | "engines": { 1207 | "node": ">=8" 1208 | } 1209 | }, 1210 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 1211 | "version": "5.0.1", 1212 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1213 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1214 | "dev": true, 1215 | "engines": { 1216 | "node": ">=8" 1217 | } 1218 | }, 1219 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 1220 | "version": "8.0.0", 1221 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1222 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1223 | "dev": true 1224 | }, 1225 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 1226 | "version": "6.0.1", 1227 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1228 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1229 | "dev": true, 1230 | "dependencies": { 1231 | "ansi-regex": "^5.0.1" 1232 | }, 1233 | "engines": { 1234 | "node": ">=8" 1235 | } 1236 | }, 1237 | "node_modules/strip-ansi": { 1238 | "version": "7.1.0", 1239 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1240 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1241 | "dev": true, 1242 | "dependencies": { 1243 | "ansi-regex": "^6.0.1" 1244 | }, 1245 | "engines": { 1246 | "node": ">=12" 1247 | }, 1248 | "funding": { 1249 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1250 | } 1251 | }, 1252 | "node_modules/strip-ansi-cjs": { 1253 | "name": "strip-ansi", 1254 | "version": "6.0.1", 1255 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1256 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1257 | "dev": true, 1258 | "dependencies": { 1259 | "ansi-regex": "^5.0.1" 1260 | }, 1261 | "engines": { 1262 | "node": ">=8" 1263 | } 1264 | }, 1265 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 1266 | "version": "5.0.1", 1267 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1268 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1269 | "dev": true, 1270 | "engines": { 1271 | "node": ">=8" 1272 | } 1273 | }, 1274 | "node_modules/supports-color": { 1275 | "version": "5.5.0", 1276 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1277 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1278 | "dev": true, 1279 | "dependencies": { 1280 | "has-flag": "^3.0.0" 1281 | }, 1282 | "engines": { 1283 | "node": ">=4" 1284 | } 1285 | }, 1286 | "node_modules/thread-stream": { 1287 | "version": "2.4.1", 1288 | "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", 1289 | "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", 1290 | "dependencies": { 1291 | "real-require": "^0.2.0" 1292 | } 1293 | }, 1294 | "node_modules/to-regex-range": { 1295 | "version": "5.0.1", 1296 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1297 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1298 | "dev": true, 1299 | "dependencies": { 1300 | "is-number": "^7.0.0" 1301 | }, 1302 | "engines": { 1303 | "node": ">=8.0" 1304 | } 1305 | }, 1306 | "node_modules/touch": { 1307 | "version": "3.1.0", 1308 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 1309 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 1310 | "dev": true, 1311 | "dependencies": { 1312 | "nopt": "~1.0.10" 1313 | }, 1314 | "bin": { 1315 | "nodetouch": "bin/nodetouch.js" 1316 | } 1317 | }, 1318 | "node_modules/ts-node": { 1319 | "version": "10.9.1", 1320 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 1321 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 1322 | "dev": true, 1323 | "dependencies": { 1324 | "@cspotcode/source-map-support": "^0.8.0", 1325 | "@tsconfig/node10": "^1.0.7", 1326 | "@tsconfig/node12": "^1.0.7", 1327 | "@tsconfig/node14": "^1.0.0", 1328 | "@tsconfig/node16": "^1.0.2", 1329 | "acorn": "^8.4.1", 1330 | "acorn-walk": "^8.1.1", 1331 | "arg": "^4.1.0", 1332 | "create-require": "^1.1.0", 1333 | "diff": "^4.0.1", 1334 | "make-error": "^1.1.1", 1335 | "v8-compile-cache-lib": "^3.0.1", 1336 | "yn": "3.1.1" 1337 | }, 1338 | "bin": { 1339 | "ts-node": "dist/bin.js", 1340 | "ts-node-cwd": "dist/bin-cwd.js", 1341 | "ts-node-esm": "dist/bin-esm.js", 1342 | "ts-node-script": "dist/bin-script.js", 1343 | "ts-node-transpile-only": "dist/bin-transpile.js", 1344 | "ts-script": "dist/bin-script-deprecated.js" 1345 | }, 1346 | "peerDependencies": { 1347 | "@swc/core": ">=1.2.50", 1348 | "@swc/wasm": ">=1.2.50", 1349 | "@types/node": "*", 1350 | "typescript": ">=2.7" 1351 | }, 1352 | "peerDependenciesMeta": { 1353 | "@swc/core": { 1354 | "optional": true 1355 | }, 1356 | "@swc/wasm": { 1357 | "optional": true 1358 | } 1359 | } 1360 | }, 1361 | "node_modules/typescript": { 1362 | "version": "5.3.2", 1363 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", 1364 | "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", 1365 | "dev": true, 1366 | "bin": { 1367 | "tsc": "bin/tsc", 1368 | "tsserver": "bin/tsserver" 1369 | }, 1370 | "engines": { 1371 | "node": ">=14.17" 1372 | } 1373 | }, 1374 | "node_modules/undefsafe": { 1375 | "version": "2.0.5", 1376 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1377 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 1378 | "dev": true 1379 | }, 1380 | "node_modules/undici-types": { 1381 | "version": "5.26.5", 1382 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1383 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 1384 | "dev": true 1385 | }, 1386 | "node_modules/v8-compile-cache-lib": { 1387 | "version": "3.0.1", 1388 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 1389 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 1390 | "dev": true 1391 | }, 1392 | "node_modules/which": { 1393 | "version": "2.0.2", 1394 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1395 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1396 | "dev": true, 1397 | "dependencies": { 1398 | "isexe": "^2.0.0" 1399 | }, 1400 | "bin": { 1401 | "node-which": "bin/node-which" 1402 | }, 1403 | "engines": { 1404 | "node": ">= 8" 1405 | } 1406 | }, 1407 | "node_modules/wrap-ansi": { 1408 | "version": "8.1.0", 1409 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 1410 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 1411 | "dev": true, 1412 | "dependencies": { 1413 | "ansi-styles": "^6.1.0", 1414 | "string-width": "^5.0.1", 1415 | "strip-ansi": "^7.0.1" 1416 | }, 1417 | "engines": { 1418 | "node": ">=12" 1419 | }, 1420 | "funding": { 1421 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1422 | } 1423 | }, 1424 | "node_modules/wrap-ansi-cjs": { 1425 | "name": "wrap-ansi", 1426 | "version": "7.0.0", 1427 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1428 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1429 | "dev": true, 1430 | "dependencies": { 1431 | "ansi-styles": "^4.0.0", 1432 | "string-width": "^4.1.0", 1433 | "strip-ansi": "^6.0.0" 1434 | }, 1435 | "engines": { 1436 | "node": ">=10" 1437 | }, 1438 | "funding": { 1439 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1440 | } 1441 | }, 1442 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 1443 | "version": "5.0.1", 1444 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1445 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1446 | "dev": true, 1447 | "engines": { 1448 | "node": ">=8" 1449 | } 1450 | }, 1451 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 1452 | "version": "4.3.0", 1453 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1454 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1455 | "dev": true, 1456 | "dependencies": { 1457 | "color-convert": "^2.0.1" 1458 | }, 1459 | "engines": { 1460 | "node": ">=8" 1461 | }, 1462 | "funding": { 1463 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1464 | } 1465 | }, 1466 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 1467 | "version": "8.0.0", 1468 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1469 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1470 | "dev": true 1471 | }, 1472 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 1473 | "version": "4.2.3", 1474 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1475 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1476 | "dev": true, 1477 | "dependencies": { 1478 | "emoji-regex": "^8.0.0", 1479 | "is-fullwidth-code-point": "^3.0.0", 1480 | "strip-ansi": "^6.0.1" 1481 | }, 1482 | "engines": { 1483 | "node": ">=8" 1484 | } 1485 | }, 1486 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 1487 | "version": "6.0.1", 1488 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1489 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1490 | "dev": true, 1491 | "dependencies": { 1492 | "ansi-regex": "^5.0.1" 1493 | }, 1494 | "engines": { 1495 | "node": ">=8" 1496 | } 1497 | }, 1498 | "node_modules/yallist": { 1499 | "version": "4.0.0", 1500 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1501 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1502 | "dev": true 1503 | }, 1504 | "node_modules/yn": { 1505 | "version": "3.1.1", 1506 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 1507 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 1508 | "dev": true, 1509 | "engines": { 1510 | "node": ">=6" 1511 | } 1512 | } 1513 | } 1514 | } 1515 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prisma-redis-middleware", 3 | "description": "Example of using Prisma Redis Middleware for caching results of queries in Redis", 4 | "engines": { 5 | "node": "^20.0.0", 6 | "npm": "^10.0.0" 7 | }, 8 | "author": "A-J Roos ", 9 | "license": "Hippocratic-3.0", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "source": "./src/index.ts", 13 | "scripts": { 14 | "build": "npm run clean && tsc", 15 | "dev": "nodemon", 16 | "clean": "rimraf dist && rimraf prisma/dev.db", 17 | "start": "node dist/index.js", 18 | "prisma:generate": "prisma generate", 19 | "prisma:migrate:dev": "prisma migrate dev" 20 | }, 21 | "dependencies": { 22 | "ioredis": "5.3.2", 23 | "pino": "8.16.2", 24 | "prisma": "5.6.0", 25 | "prisma-redis-middleware": "4.8.0" 26 | }, 27 | "devDependencies": { 28 | "@prisma/client": "5.6.0", 29 | "@types/ioredis": "4.28.10", 30 | "@types/node": "20.10.3", 31 | "nodemon": "3.0.2", 32 | "rimraf": "5.0.5", 33 | "ts-node": "10.9.1", 34 | "typescript": "5.3.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/prisma/migrations/20220407135605_initial_migration/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "email" TEXT NOT NULL, 5 | "name" TEXT, 6 | "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 7 | ); 8 | 9 | -- CreateIndex 10 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 11 | -------------------------------------------------------------------------------- /example/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /example/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "sqlite" 7 | url = "file:./dev.db" 8 | } 9 | 10 | model User { 11 | id Int @id @default(autoincrement()) 12 | email String @unique 13 | name String? 14 | createdAt DateTime @default(now()) 15 | } 16 | -------------------------------------------------------------------------------- /example/src/index.ts: -------------------------------------------------------------------------------- 1 | const { PrismaClient } = require("@prisma/client"); 2 | const { createPrismaRedisCache } = require("prisma-redis-middleware"); 3 | const Redis = require("ioredis"); 4 | const logger = require("pino")({ level: "debug" }); 5 | 6 | const redis = new Redis(); // Uses default options for Redis connection 7 | const prisma = new PrismaClient(); 8 | 9 | const cacheMiddleware = createPrismaRedisCache({ 10 | models: [ 11 | { model: "User", cacheTime: 300 }, 12 | { model: "Post", cacheKey: "Article" }, 13 | ], 14 | storage: { type: "redis", options: { client: redis, invalidation: { invalidationTTL: 300 } } }, 15 | cacheTime: 60, 16 | onHit: (key: string) => { 17 | console.log("Hit: ✅", key); 18 | }, 19 | onMiss: (key: string) => { 20 | console.log("Miss: ❌", key); 21 | }, 22 | }); 23 | 24 | prisma.$use(cacheMiddleware); 25 | 26 | async function main() { 27 | // Create 2 users in database 28 | await prisma.user.create({ data: { name: "John", email: "john@email.com" } }); 29 | await prisma.user.create({ data: { name: "Mary", email: "mary@email.com" } }); 30 | 31 | // Find users to test cache 32 | await prisma.user.findMany({ 33 | where: { 34 | email: { 35 | endsWith: "email.com", 36 | }, 37 | }, 38 | }); 39 | 40 | // Invalidate Users cache by running mutation method 41 | await prisma.user.update({ where: { email: "john@email.com" }, data: { name: "Alice", email: "alice@email.com" } }); 42 | 43 | await prisma.user.count(); 44 | await prisma.user.count(); 45 | await prisma.user.count(); 46 | 47 | await prisma.user.findMany({ 48 | where: { 49 | email: { 50 | endsWith: "email.com", 51 | }, 52 | }, 53 | }); 54 | await prisma.user.findMany({ 55 | where: { 56 | email: { 57 | endsWith: "email.com", 58 | }, 59 | }, 60 | }); 61 | } 62 | 63 | main() 64 | .finally(async () => { 65 | await prisma.$disconnect(); 66 | }) 67 | .catch((err) => { 68 | console.error(err); 69 | }); 70 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "exclude": ["node_modules"], 4 | "compilerOptions": { 5 | "lib": ["es2019"], 6 | "declaration": true, 7 | "module": "CommonJS", 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "outDir": "./dist", 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "moduleResolution": "node", 17 | "esModuleInterop": true, 18 | "forceConsistentCasingInFileNames": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prisma-redis-middleware", 3 | "author": "A-J Roos ", 4 | "version": "4.8.0", 5 | "description": "Prisma Middleware for caching results of queries in Redis", 6 | "license": "Hippocratic-3.0", 7 | "main": "./dist/index.js", 8 | "types": "./dist/index.d.ts", 9 | "source": "./src/index.ts", 10 | "module": "./dist/prisma-redis-middleware.esm.js", 11 | "files": [ 12 | "dist", 13 | "src" 14 | ], 15 | "scripts": { 16 | "start": "tsdx watch", 17 | "build": "tsdx build", 18 | "clean": "rimraf dist/ coverage/", 19 | "prepare": "tsdx build", 20 | "test": "vitest run", 21 | "test:watch": "vitest -w", 22 | "coverage": "c8 --reporter=lcov vitest run --coverage", 23 | "check-types": "tsc", 24 | "prettier": "prettier --cache \"**/**/*.+(json|ts)\"", 25 | "format": "npm run prettier -- --write", 26 | "check-format": "npm run prettier -- --list-different", 27 | "validate": "npm-run-all --parallel check-types check-format build", 28 | "size": "size-limit", 29 | "analyze": "size-limit --why" 30 | }, 31 | "dependencies": { 32 | "async-cache-dedupe": "2.0.0", 33 | "ioredis": "5.3.2" 34 | }, 35 | "devDependencies": { 36 | "@ianvs/prettier-plugin-sort-imports": "4.1.1", 37 | "@prisma/client": "5.6.0", 38 | "@size-limit/preset-small-lib": "11.0.0", 39 | "@types/ioredis-mock": "8.2.5", 40 | "@vitest/coverage-c8": "0.33.0", 41 | "@vitest/coverage-v8": "0.34.6", 42 | "c8": "8.0.1", 43 | "husky": "8.0.3", 44 | "ioredis-mock": "8.9.0", 45 | "lint-staged": "15.2.0", 46 | "npm-run-all": "4.1.5", 47 | "prettier": "3.1.0", 48 | "rimraf": "5.0.5", 49 | "size-limit": "11.0.0", 50 | "tsdx": "0.14.1", 51 | "tslib": "2.6.2", 52 | "typescript": "5.3.2", 53 | "vitest": "0.34.6" 54 | }, 55 | "husky": { 56 | "hooks": { 57 | "pre-commit": "npm run check-types && lint-staged && npm run build" 58 | } 59 | }, 60 | "lint-staged": { 61 | "**/**/*.+(ts)": [ 62 | "prettier --write", 63 | "git add" 64 | ] 65 | }, 66 | "size-limit": [ 67 | { 68 | "path": "dist/prisma-redis-middleware.cjs.production.min.js", 69 | "limit": "16 KB" 70 | }, 71 | { 72 | "path": "dist/prisma-redis-middleware.esm.js", 73 | "limit": "16 KB" 74 | } 75 | ], 76 | "engines": { 77 | "node": "^16.x || ^18.x || ^20.x", 78 | "npm": "^10.0.0" 79 | }, 80 | "repository": { 81 | "type": "git", 82 | "url": "git+https://github.com/Asjas/prisma-redis-middleware.git" 83 | }, 84 | "keywords": [ 85 | "prisma", 86 | "redis", 87 | "prisma-caching", 88 | "redis-caching", 89 | "prisma-middleware", 90 | "caching" 91 | ], 92 | "bugs": { 93 | "url": "https://github.com/Asjas/prisma-redis-middleware/issues" 94 | }, 95 | "homepage": "https://github.com/Asjas/prisma-redis-middleware#readme" 96 | } 97 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "dependencyDashboard": false, 4 | "extends": ["config:base"], 5 | "timezone": "Africa/Johannesburg", 6 | "assignees": ["A-J Roos"], 7 | "reviewers": ["A-J Roos"], 8 | "labels": ["renovate"], 9 | "rangeStrategy": "update-lockfile", 10 | "packageRules": [ 11 | { 12 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 13 | "automerge": true 14 | } 15 | ], 16 | "lockFileMaintenance": { 17 | "enabled": true, 18 | "recreateClosed": true, 19 | "rebaseStalePrs": true, 20 | "branchTopic": "lock-file-maintenance", 21 | "commitMessageAction": "Lock file maintenance", 22 | "schedule": ["before 5am on monday"], 23 | "prBodyDefinitions": { 24 | "Change": "All locks refreshed" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/async-cache-dedupe.d.ts: -------------------------------------------------------------------------------- 1 | declare module "async-cache-dedupe"; 2 | -------------------------------------------------------------------------------- /src/cacheMethods.ts: -------------------------------------------------------------------------------- 1 | import type { PrismaMutationAction, PrismaQueryAction } from "./types"; 2 | 3 | export const defaultCacheMethods: PrismaQueryAction[] = [ 4 | "findUnique", 5 | "findFirst", 6 | "findMany", 7 | "count", 8 | "aggregate", 9 | "groupBy", 10 | "findRaw", 11 | "aggregateRaw", 12 | ]; 13 | 14 | export const defaultMutationMethods: PrismaMutationAction[] = [ 15 | "create", 16 | "createMany", 17 | "update", 18 | "updateMany", 19 | "upsert", 20 | "delete", 21 | "deleteMany", 22 | "executeRaw", 23 | "executeRawUnsafe" 24 | ]; 25 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { createCache } from "async-cache-dedupe"; 2 | 3 | import { defaultCacheMethods, defaultMutationMethods } from "./cacheMethods"; 4 | import type { 5 | CreatePrismaRedisCache, 6 | FetchFromPrisma, 7 | Middleware, 8 | MiddlewareParams, 9 | PrismaAction, 10 | PrismaMutationAction, 11 | PrismaQueryAction, 12 | Result, 13 | } from "./types"; 14 | 15 | const DEFAULT_CACHE_TIME = 0; 16 | 17 | export const createPrismaRedisCache = ({ 18 | models, 19 | onDedupe, 20 | onError, 21 | onHit, 22 | onMiss, 23 | storage, 24 | cacheTime = DEFAULT_CACHE_TIME, 25 | excludeModels = [], 26 | excludeMethods = [], 27 | transformer, 28 | }: CreatePrismaRedisCache) => { 29 | // Default options for "async-cache-dedupe" 30 | const cacheOptions = { 31 | onDedupe, 32 | onError, 33 | onHit, 34 | onMiss, 35 | storage, 36 | ttl: cacheTime, 37 | transformer, 38 | }; 39 | 40 | const cache: any = createCache(cacheOptions); 41 | 42 | const middleware: Middleware = async (params, next) => { 43 | let result: Result; 44 | 45 | // This function is used by `async-cache-dedupe` to fetch data when the cache is empty 46 | const fetchFromPrisma: FetchFromPrisma = async (params) => { 47 | return next(params); 48 | }; 49 | 50 | // Do not cache any Prisma method specified in the defaultExcludeCacheMethods option 51 | const excludedCacheMethods: PrismaAction[] = defaultCacheMethods.filter((cacheMethod) => { 52 | return excludeMethods.includes(cacheMethod); 53 | }); 54 | 55 | // Do not cache any Prisma method that has been excluded 56 | if (!excludedCacheMethods?.includes(params.action)) { 57 | // Add a cache function for each model specified in the models option 58 | models?.forEach(({ model, cacheTime, cacheKey, excludeMethods }) => { 59 | // Only define the cache function for a model if it doesn't exist yet and hasn't been excluded 60 | if ( 61 | !cache[model] && 62 | model === params.model && 63 | !excludeModels?.includes(params.model) && 64 | !excludeMethods?.includes(params.action as PrismaQueryAction) 65 | ) { 66 | cache.define( 67 | model, 68 | { 69 | references: ({ params }: { params: MiddlewareParams }, key: string) => { 70 | return [`${cacheKey || params.model}~${key}`]; 71 | }, 72 | ttl: cacheTime || cacheOptions.ttl, 73 | }, 74 | async function modelsFetch({ cb, params }: { cb: FetchFromPrisma; params: MiddlewareParams }) { 75 | result = await cb(params); 76 | 77 | return result; 78 | }, 79 | ); 80 | } 81 | }); 82 | 83 | // For each defined model in `models` we check if they defined any caching methods to be excluded 84 | const excludedCacheMethodsInModels = models?.find(({ model, excludeMethods }) => { 85 | return model === params.model && excludeMethods?.length; 86 | }); 87 | 88 | // Do not define a cache function for any Prisma model if it already exists 89 | // Do not define the cache function for a model if it was excluded in `defaultExcludeCacheModels` 90 | // Do not define a cache function if the Prisma method was exluded in `models` 91 | if ( 92 | !cache[params.model] && 93 | !excludeModels?.includes(params.model) && 94 | !excludedCacheMethodsInModels?.excludeMethods?.includes(params.action as PrismaQueryAction) 95 | ) { 96 | cache.define( 97 | params.model, 98 | { 99 | references: ({ params }: { params: MiddlewareParams }, key: string) => { 100 | return [`${params.model}~${key}`]; 101 | }, 102 | }, 103 | async function modelFetch({ cb, params }: { cb: FetchFromPrisma; params: MiddlewareParams }) { 104 | result = await cb(params); 105 | 106 | return result; 107 | }, 108 | ); 109 | } 110 | } 111 | 112 | // Get the cache function relating to the Prisma model 113 | const cacheFunction = cache[params.model]; 114 | 115 | // Only cache the data if the Prisma model hasn't been excluded and if the Prisma method wasn't excluded either 116 | if ( 117 | !excludeModels?.includes(params.model) && 118 | !excludedCacheMethods?.includes(params.action) && 119 | !defaultMutationMethods?.includes(params.action as PrismaMutationAction) && 120 | typeof cacheFunction === "function" 121 | ) { 122 | try { 123 | result = await cacheFunction({ cb: fetchFromPrisma, params }); 124 | } catch (err) { 125 | // If we fail to fetch it from the cache (network error, etc.) we will query it from the database 126 | result = await fetchFromPrisma(params); 127 | 128 | console.error(err); 129 | } 130 | } else { 131 | // Query the database for any Prisma method (mutation method) or Prisma model we excluded from the cache 132 | result = await fetchFromPrisma(params); 133 | 134 | // If we successfully executed the Mutation we clear and invalidate the cache for the Prisma model 135 | if (defaultMutationMethods.includes(params.action as PrismaMutationAction)) { 136 | await cache.invalidateAll(`*${params.model}~*`); 137 | 138 | await Promise.all( 139 | (models || []) 140 | .filter(({ model }) => model === params.model) 141 | .map(async ({ invalidateRelated }) => { 142 | if (invalidateRelated) { 143 | await Promise.all( 144 | invalidateRelated.map(async (relatedModel) => cache.invalidateAll(`*${relatedModel}~*`)), 145 | ); 146 | } 147 | }), 148 | ); 149 | } 150 | } 151 | 152 | return result; 153 | }; 154 | 155 | return middleware; 156 | }; 157 | -------------------------------------------------------------------------------- /src/prisma-client.d.ts: -------------------------------------------------------------------------------- 1 | import "@prisma/client"; 2 | 3 | declare module "@prisma/client" { 4 | type PrismaQueryAction = 5 | | "findFirst" 6 | | "findFirstOrThrow" 7 | | "findUnique" 8 | | "findUniqueOrThrow" 9 | | "findMany" 10 | | "aggregate" 11 | | "count" 12 | | "groupBy" 13 | | "findRaw" 14 | | "runCommandRaw" 15 | | "queryRaw" 16 | | "aggregateRaw"; 17 | 18 | type PrismaMutationAction = 19 | | "create" 20 | | "createMany" 21 | | "update" 22 | | "updateMany" 23 | | "upsert" 24 | | "delete" 25 | | "deleteMany" 26 | | "executeRaw" 27 | | "executeRawUnsafe"; 28 | 29 | type PrismaAction = PrismaQueryAction | PrismaMutationAction; 30 | 31 | namespace Prisma { 32 | type ModelName = string; 33 | 34 | interface MiddlewareParams { 35 | model: string; 36 | action: PrismaAction; 37 | args: any; 38 | dataPath: string[]; 39 | runInTransaction: boolean; 40 | } 41 | } 42 | 43 | export { Prisma }; 44 | } 45 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type Redis from "ioredis"; 2 | 3 | import type { Prisma } from "@prisma/client"; 4 | 5 | export type PrismaQueryAction = 6 | | "findFirst" 7 | | "findFirstOrThrow" 8 | | "findUnique" 9 | | "findUniqueOrThrow" 10 | | "findMany" 11 | | "aggregate" 12 | | "count" 13 | | "groupBy" 14 | | "findRaw" 15 | | "runCommandRaw" 16 | | "queryRaw" 17 | | "aggregateRaw"; 18 | 19 | export type PrismaMutationAction = 20 | | "create" 21 | | "createMany" 22 | | "update" 23 | | "updateMany" 24 | | "upsert" 25 | | "delete" 26 | | "deleteMany" 27 | | "executeRaw" 28 | | "executeRawUnsafe"; 29 | 30 | export type PrismaAction = PrismaQueryAction | PrismaMutationAction; 31 | 32 | export type Result = Record | Record[]; 33 | 34 | /** 35 | * These options are being passed in to the middleware as "params" 36 | * https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#params 37 | */ 38 | export type MiddlewareParams = Prisma.MiddlewareParams; 39 | 40 | // https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#use 41 | export type Middleware = ( 42 | params: MiddlewareParams, 43 | next: (params: MiddlewareParams) => Promise, 44 | ) => Promise; 45 | 46 | export type FetchFromPrisma = (params: MiddlewareParams) => Promise; 47 | 48 | export type RedisMemoryStorage = { 49 | type: "redis"; 50 | options?: RedisMemoryOptions; 51 | } 52 | 53 | export type RedisMemoryOptions = { 54 | client: Redis; 55 | invalidation?: boolean | { referencesTTL?: number }; 56 | log?: any; 57 | }; 58 | 59 | export type MemoryStorage = { 60 | type: "memory"; 61 | options?: MemoryStorageOptions; 62 | } 63 | 64 | export type MemoryStorageOptions = { 65 | size?: number; 66 | invalidation?: boolean; 67 | log?: any; 68 | }; 69 | 70 | export type TtlFunction = (data: any) => number; 71 | 72 | export type CreatePrismaRedisCache = { 73 | models?: { 74 | model: string & {} | Prisma.ModelName; 75 | cacheKey?: string; 76 | cacheTime?: number | TtlFunction; 77 | excludeMethods?: PrismaQueryAction[]; 78 | invalidateRelated?: string[] | Prisma.ModelName[]; 79 | }[]; 80 | storage?: RedisMemoryStorage | MemoryStorage; 81 | cacheTime?: number; 82 | excludeModels?: string[] | Prisma.ModelName[]; 83 | excludeMethods?: PrismaQueryAction[]; 84 | onError?: (key: string) => void; 85 | onHit?: (key: string) => void; 86 | onMiss?: (key: string) => void; 87 | onDedupe?: (key: string) => void; 88 | transformer?: { 89 | serialize: (data: any) => any; 90 | deserialize: (data: any) => any; 91 | }; 92 | }; 93 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, assert, describe, expect, test } from "vitest"; 2 | 3 | import type Redis from "ioredis"; 4 | 5 | import MockRedis from "ioredis-mock"; 6 | 7 | import { createPrismaRedisCache } from "../src"; 8 | 9 | // Create the mock Redis instance we need 10 | // Do some funky shit to get TypeScript to be happy 😫 11 | const mockRedis: unknown = new MockRedis(); 12 | const redis = mockRedis as Redis; 13 | 14 | afterEach(async () => { 15 | await redis.flushall(); 16 | }); 17 | 18 | // Do some setup stuff 19 | const dbValue = { key: "test result" }; 20 | const model1 = "User"; 21 | const model2 = "Post"; 22 | const action1 = "findUnique"; 23 | const action2 = "findFirst"; 24 | 25 | describe.each<{ 26 | args: { where: { foo: string } }; 27 | cacheTime: number; 28 | cacheKey1: string; 29 | cacheKey2: string; 30 | next: () => Promise; 31 | }>([ 32 | { 33 | args: { where: { foo: "bar" } }, 34 | cacheTime: 2000, // 2 seconds 35 | cacheKey1: `${model1}~{"params":{"action":"${action1}","args":{"where":{"foo":"bar"}},"dataPath":[],"model":"${model1}","runInTransaction":false}}`, 36 | cacheKey2: `${model2}~{"params":{"action":"${action2}","args":{"where":{"foo":"bar"}},"dataPath":[],"model":"${model2}","runInTransaction":false}}`, 37 | next: () => Promise.resolve(dbValue), 38 | }, 39 | ])("createPrismaRedisCache function", ({ args, cacheTime, cacheKey1, cacheKey2, next }) => { 40 | test("should cache a single Prisma model", async () => { 41 | const middleware = createPrismaRedisCache({ 42 | cacheTime, 43 | storage: { type: "redis", options: { client: redis } }, 44 | }); 45 | 46 | // Run a "fake" User Prisma query 47 | await middleware( 48 | { 49 | args, 50 | action: action1, 51 | model: model1, 52 | dataPath: [], 53 | runInTransaction: false, 54 | }, 55 | next, 56 | ); 57 | 58 | // Test if the data exists in the cache 59 | expect(JSON.parse((await redis.get(cacheKey1)) as string)).toMatchObject(dbValue); 60 | }); 61 | 62 | test("should cache multiple Prisma models in cache", async () => { 63 | const middleware = createPrismaRedisCache({ 64 | models: [ 65 | { model: model1, cacheTime }, 66 | { model: model2, cacheTime }, 67 | ], 68 | storage: { type: "redis", options: { client: redis } }, 69 | }); 70 | 71 | // Run a "fake" User Prisma query 72 | await middleware( 73 | { 74 | args, 75 | action: action1, 76 | model: model1, 77 | dataPath: [], 78 | runInTransaction: false, 79 | }, 80 | next, 81 | ); 82 | 83 | // Run a "fake" Post Prisma query 84 | await middleware( 85 | { 86 | args, 87 | action: action2, 88 | model: model2, 89 | dataPath: [], 90 | runInTransaction: false, 91 | }, 92 | next, 93 | ); 94 | 95 | // Test if the data exists in the cache 96 | expect(JSON.parse((await redis.get(cacheKey1)) as string)).toMatchObject(dbValue); 97 | expect(JSON.parse((await redis.get(cacheKey2)) as string)).toMatchObject(dbValue); 98 | }); 99 | 100 | test("should use custom cacheKey when caching a Prisma model", async () => { 101 | const customCacheKey = "Article"; 102 | const cacheKey = `${customCacheKey}~{"params":{"action":"${action1}","args":{"where":{"foo":"bar"}},"dataPath":[],"model":${customCacheKey},"runInTransaction":false}}`; 103 | 104 | const middleware = createPrismaRedisCache({ 105 | models: [{ model: model1, cacheKey: customCacheKey }], 106 | storage: { type: "redis", options: { client: redis } }, 107 | cacheTime, 108 | }); 109 | 110 | // Run a "fake" User Prisma query 111 | await middleware( 112 | { 113 | args, 114 | action: action1, 115 | model: model1, 116 | dataPath: [], 117 | runInTransaction: false, 118 | }, 119 | next, 120 | ); 121 | 122 | // Test if the query was skipped and does not exist in cache 123 | assert.equal(JSON.parse((await redis.get(cacheKey)) as string), null); 124 | }); 125 | 126 | test("should exclude Prisma action from being cached with individual model excludeMethods", async () => { 127 | const middleware = createPrismaRedisCache({ 128 | models: [{ model: model1, cacheTime, excludeMethods: [action1] }], 129 | storage: { type: "redis", options: { client: redis } }, 130 | cacheTime, 131 | }); 132 | 133 | // Run a "fake" Post Prisma query 134 | await middleware( 135 | { 136 | args, 137 | action: action2, 138 | model: model2, 139 | dataPath: [], 140 | runInTransaction: false, 141 | }, 142 | next, 143 | ); 144 | 145 | // Run a "fake" User Prisma query 146 | await middleware( 147 | { 148 | args, 149 | action: action1, 150 | model: model1, 151 | dataPath: [], 152 | runInTransaction: false, 153 | }, 154 | next, 155 | ); 156 | 157 | // Test if the query was skipped and does not exist in cache 158 | assert.equal(JSON.parse((await redis.get(cacheKey1)) as string), null); 159 | expect(JSON.parse((await redis.get(cacheKey2)) as string)).toMatchObject(dbValue); 160 | }); 161 | 162 | test("should exclude a Prisma method from being cached with default excludeMethods", async () => { 163 | const middleware = createPrismaRedisCache({ 164 | models: [{ model: model1 }], 165 | storage: { type: "redis", options: { client: redis } }, 166 | cacheTime, 167 | excludeMethods: [action1], 168 | }); 169 | 170 | // Run a "fake" User Prisma query 171 | await middleware( 172 | { 173 | args, 174 | action: action1, 175 | model: model1, 176 | dataPath: [], 177 | runInTransaction: false, 178 | }, 179 | next, 180 | ); 181 | 182 | // Run a "fake" Post Prisma query 183 | await middleware( 184 | { 185 | args, 186 | action: action2, 187 | model: model2, 188 | dataPath: [], 189 | runInTransaction: false, 190 | }, 191 | next, 192 | ); 193 | 194 | // Test if the query was skipped and does not exist in cache 195 | assert.equal(JSON.parse((await redis.get(cacheKey1)) as string), null); 196 | // Test that (non-excluded) queries are still cached 197 | expect(JSON.parse((await redis.get(cacheKey2)) as string)).toMatchObject(dbValue); 198 | }); 199 | 200 | test("should exclude a Prisma model from being cached with excludeModels", async () => { 201 | const middleware = createPrismaRedisCache({ 202 | storage: { type: "redis", options: { client: redis } }, 203 | cacheTime, 204 | excludeModels: [model1], 205 | }); 206 | 207 | // Run a "fake" User Prisma query 208 | await middleware( 209 | { 210 | args, 211 | action: action1, 212 | model: model1, 213 | dataPath: [], 214 | runInTransaction: false, 215 | }, 216 | next, 217 | ); 218 | 219 | // Run a "fake" Post Prisma query 220 | await middleware( 221 | { 222 | args, 223 | action: action2, 224 | model: model2, 225 | dataPath: [], 226 | runInTransaction: false, 227 | }, 228 | next, 229 | ); 230 | 231 | // Test if the Model was skipped and does not exist in cache 232 | assert.equal(JSON.parse((await redis.get(cacheKey1)) as string), null); 233 | // Make sure that other (non-excluded) models are still cached 234 | expect(JSON.parse((await redis.get(cacheKey2)) as string)).toMatchObject(dbValue); 235 | }); 236 | 237 | test("should invalidate a single Prisma model cache after data mutation", async () => { 238 | const middleware = createPrismaRedisCache({ 239 | storage: { type: "redis", options: { client: redis, invalidation: true } }, 240 | cacheTime, 241 | }); 242 | 243 | // Run a "fake" User Prisma query 244 | await middleware( 245 | { 246 | args, 247 | action: action1, 248 | model: model1, 249 | dataPath: [], 250 | runInTransaction: false, 251 | }, 252 | next, 253 | ); 254 | 255 | // Run a "fake" Post Prisma query 256 | await middleware( 257 | { 258 | args, 259 | action: action2, 260 | model: model2, 261 | dataPath: [], 262 | runInTransaction: false, 263 | }, 264 | next, 265 | ); 266 | 267 | // Test if data exists in the cache 268 | expect(JSON.parse((await redis.get(cacheKey1)) as string)).toMatchObject(dbValue); 269 | expect(JSON.parse((await redis.get(cacheKey2)) as string)).toMatchObject(dbValue); 270 | 271 | // Run a "fake" User Prisma mutation 272 | await middleware( 273 | { 274 | args, 275 | action: "create", 276 | model: model1, 277 | dataPath: [], 278 | runInTransaction: false, 279 | }, 280 | next, 281 | ); 282 | 283 | // Test if the cache was invalidated and cleared properly 284 | assert.equal(JSON.parse((await redis.get(cacheKey1)) as string), null); 285 | // Test that we keep other cached queries that shouldn't be cleared 286 | expect(JSON.parse((await redis.get(cacheKey2)) as string)).toMatchObject(dbValue); 287 | }); 288 | 289 | test("should invalidate a Prisma model cache and related Prisma models after data mutation", async () => { 290 | const middleware = createPrismaRedisCache({ 291 | models: [ 292 | { 293 | model: model1, 294 | invalidateRelated: [model2], 295 | }, 296 | ], 297 | storage: { type: "redis", options: { client: redis, invalidation: true } }, 298 | cacheTime, 299 | }); 300 | 301 | // Run a "fake" User Prisma query 302 | await middleware( 303 | { 304 | args, 305 | action: action1, 306 | model: model1, 307 | dataPath: [], 308 | runInTransaction: false, 309 | }, 310 | next, 311 | ); 312 | 313 | // Run a "fake" Post Prisma query 314 | await middleware( 315 | { 316 | args, 317 | action: action2, 318 | model: model2, 319 | dataPath: [], 320 | runInTransaction: false, 321 | }, 322 | next, 323 | ); 324 | 325 | // Test if data exists in the cache 326 | expect(JSON.parse((await redis.get(cacheKey1)) as string)).toMatchObject(dbValue); 327 | expect(JSON.parse((await redis.get(cacheKey2)) as string)).toMatchObject(dbValue); 328 | 329 | // Run a "fake" User Prisma mutation 330 | await middleware( 331 | { 332 | args, 333 | action: "create", 334 | model: model1, 335 | dataPath: [], 336 | runInTransaction: false, 337 | }, 338 | next, 339 | ); 340 | 341 | // Test if the cache was invalidated and cleared properly 342 | assert.equal(JSON.parse((await redis.get(cacheKey1)) as string), null); 343 | assert.equal(JSON.parse((await redis.get(cacheKey2)) as string), null); 344 | }); 345 | 346 | test("should not invalidate a Prisma model if cache method is excluded", async () => { 347 | const middleware = createPrismaRedisCache({ 348 | storage: { type: "redis", options: { client: redis, invalidation: true } }, 349 | cacheTime, 350 | excludeMethods: ["findFirst"], 351 | }); 352 | 353 | // Run a "fake" User Prisma query 354 | await middleware( 355 | { 356 | args, 357 | action: action1, 358 | model: model1, 359 | dataPath: [], 360 | runInTransaction: false, 361 | }, 362 | next, 363 | ); 364 | 365 | // Run a "fake" Post Prisma query 366 | await middleware( 367 | { 368 | args, 369 | action: action2, 370 | model: model2, 371 | dataPath: [], 372 | runInTransaction: false, 373 | }, 374 | next, 375 | ); 376 | 377 | // Test if the cached query was fetched from the cache 378 | expect(JSON.parse((await redis.get(cacheKey1)) as string)).toMatchObject(dbValue); 379 | // Test that the excluded cache method was not cached 380 | assert.equal(JSON.parse((await redis.get(cacheKey2)) as string), null); 381 | }); 382 | }); 383 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "module": "es2015", 6 | "lib": ["es2019"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./src", 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "moduleResolution": "node", 17 | "jsx": "react", 18 | "esModuleInterop": true, 19 | "skipLibCheck": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "noEmit": true 22 | } 23 | } 24 | --------------------------------------------------------------------------------