├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── changelog.yaml │ └── pr-check.yaml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── package-lock.json ├── package.json ├── src ├── cache │ ├── Cache.ts │ └── engine │ │ ├── MemoryCacheEngine.ts │ │ └── RedisCacheEngine.ts ├── extend │ ├── aggregate.ts │ └── query.ts ├── index.ts ├── key.ts ├── types.ts └── version.ts ├── tests ├── cache-debug.test.ts ├── cache-memory.test.ts ├── cache-options.test.ts ├── cache-redis.test.ts ├── key.test.ts ├── models │ ├── Story.ts │ └── User.ts └── mongo │ ├── .gitignore │ └── server.ts ├── tsconfig.json └── vite.config.mts /.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 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information):** 28 | 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | day: "monday" 13 | ignore: 14 | - dependency-name: "sort-keys" 15 | commit-message: 16 | prefix: "Update dependencies" 17 | pull-request-branch-name: 18 | separator: "-" 19 | groups: 20 | all-dependencies: 21 | patterns: 22 | - "*" 23 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yaml: -------------------------------------------------------------------------------- 1 | name: Changelog 2 | 3 | on: 4 | workflow_dispatch: 5 | # Trigger analysis when pushing in master or pull requests, 6 | # and when creating a pull request. 7 | release: 8 | types: 9 | - published 10 | 11 | jobs: 12 | changelog: 13 | name: Update changelog 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | ref: main 20 | - name: Changelog 21 | uses: rhysd/changelog-from-release/action@v3 22 | with: 23 | file: CHANGELOG.md 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/pr-check.yaml: -------------------------------------------------------------------------------- 1 | name: PR check 2 | 3 | on: 4 | workflow_dispatch: 5 | # Trigger analysis when pushing in master or pull requests, 6 | # and when creating a pull request. 7 | push: 8 | branches: 9 | - master 10 | - feature* 11 | 12 | pull_request: 13 | types: [opened, synchronize, reopened] 14 | 15 | jobs: 16 | tests: 17 | name: Coverage 18 | runs-on: ubuntu-latest 19 | 20 | # Service containers to run with `container-job` 21 | services: 22 | # Label used to access the service container 23 | redis: 24 | # Docker Hub image 25 | image: redis 26 | # Set health checks to wait until redis has started 27 | options: >- 28 | --health-cmd "redis-cli ping" 29 | --health-interval 10s 30 | --health-timeout 5s 31 | --health-retries 5 32 | ports: 33 | # Maps port 6379 on service container to the host 34 | - 6379:6379 35 | 36 | strategy: 37 | matrix: 38 | node-version: [18.x, 20.x] 39 | mongoose-version: 40 | [ 41 | [mongoose@6.12.8, bson@^4.7.2], 42 | [mongoose@7.6.11, bson@^5.5.0], 43 | [mongoose@latest, bson@^6.10.1], 44 | ] 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v4 48 | with: 49 | # Disabling shallow clone is recommended for improving relevancy of reporting 50 | fetch-depth: 0 51 | 52 | - name: Use Node.js ${{ matrix.node-version }} 53 | uses: actions/setup-node@v4 54 | with: 55 | node-version: ${{ matrix.node-version }} 56 | cache: npm 57 | 58 | - name: Installing dependencies 59 | run: npm ci 60 | 61 | - name: Installing ${{ matrix.mongoose-version[0] }} {{ matrix.mongoose-version[1] }} 62 | run: npm i ${{ matrix.mongoose-version[0] }} ${{ matrix.mongoose-version[1] }} 63 | 64 | - name: Coverage 65 | run: npm run test 66 | env: 67 | # The hostname used to communicate with the Redis service container 68 | REDIS_HOST: localhost 69 | # The default Redis port 70 | REDIS_PORT: 6379 71 | 72 | sonar: 73 | name: Biome, Coverage & Sonar 74 | runs-on: ubuntu-latest 75 | 76 | # Service containers to run with `container-job` 77 | services: 78 | # Label used to access the service container 79 | redis: 80 | # Docker Hub image 81 | image: redis 82 | # Set health checks to wait until redis has started 83 | options: >- 84 | --health-cmd "redis-cli ping" 85 | --health-interval 10s 86 | --health-timeout 5s 87 | --health-retries 5 88 | ports: 89 | # Maps port 6379 on service container to the host 90 | - 6379:6379 91 | 92 | strategy: 93 | matrix: 94 | node-version: [22.x] 95 | steps: 96 | - name: Checkout 97 | uses: actions/checkout@v4 98 | with: 99 | # Disabling shallow clone is recommended for improving relevancy of reporting 100 | fetch-depth: 0 101 | 102 | - name: Use Node.js ${{ matrix.node-version }} 103 | uses: actions/setup-node@v4 104 | with: 105 | node-version: ${{ matrix.node-version }} 106 | cache: npm 107 | 108 | - name: Installing dependencies 109 | run: npm ci 110 | 111 | - name: Biome 112 | run: npm run biome 113 | 114 | - name: Coverage 115 | run: npm run test 116 | env: 117 | # The hostname used to communicate with the Redis service container 118 | REDIS_HOST: localhost 119 | # The default Redis port 120 | REDIS_PORT: 6379 121 | 122 | - name: Scan 123 | if: env.SONAR_TOKEN 124 | uses: SonarSource/sonarqube-scan-action@master 125 | env: 126 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 127 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 128 | with: 129 | args: > 130 | -Dsonar.organization=ilovepixelart 131 | -Dsonar.projectName=ts-cache-mongoose 132 | -Dsonar.projectKey=ilovepixelart_ts-cache-mongoose 133 | -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info 134 | -Dsonar.sources=src 135 | -Dsonar.tests=tests 136 | -Dsonar.test.exclusions=tests/** 137 | -Dsonar.coverage.exclusions=tests/** 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # System files 2 | .DS_Store 3 | Thumbs.db 4 | 5 | # Dependency directory 6 | node_modules 7 | dist 8 | coverage 9 | 10 | # npm debug logs 11 | npm-debug.log 12 | 13 | # Webstorm 14 | .idea 15 | .env 16 | 17 | # In-memory mongo config 18 | /globalConfig.json 19 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "streetsidesoftware.code-spell-checker", 4 | "mikestead.dotenv", 5 | "biomejs.biome", 6 | "eamodio.gitlens", 7 | "vitest.explorer", 8 | "DavidAnson.vscode-markdownlint" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "explorer.fileNesting.enabled": true, 3 | "explorer.fileNesting.patterns": { 4 | "tsconfig.json": "tsconfig.*.json, env.d.ts", 5 | "package.json": "package-lock.json, .gitignore", 6 | "README.md": "CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md" 7 | }, 8 | "editor.tabSize": 2, 9 | "editor.insertSpaces": true, 10 | "editor.defaultFormatter": "biomejs.biome", 11 | "editor.codeActionsOnSave": { 12 | "quickfix.biome": "explicit", 13 | "source.organizeImports.biome": "explicit" 14 | }, 15 | "cSpell.words": [ 16 | "autosync", 17 | "biomejs", 18 | "bson", 19 | "dbaeumer", 20 | "Dsonar", 21 | "EJSON", 22 | "ilovepixelart", 23 | "jsonpatch", 24 | "lcov", 25 | "linebreak", 26 | "nargs", 27 | "nodenext", 28 | "nosql", 29 | "npmignore", 30 | "parens", 31 | "pkgroll", 32 | "rhysd", 33 | "setex", 34 | "singleline", 35 | "sonarcloud", 36 | "sonarjs", 37 | "sonarqube", 38 | "sonarsource", 39 | "virtuals", 40 | "Webstorm" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # [v1.7.3](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.7.3) - 2025-04-05 3 | 4 | - Merge pull request [#287](https://github.com/ilovepixelart/ts-cache-mongoose/issues/287) from ilovepixelart/feature/dep 23469b7 5 | - Dep dbe8573 6 | - Merge pull request [#286](https://github.com/ilovepixelart/ts-cache-mongoose/issues/286) from ilovepixelart/feature/dep 85c0988 7 | - Dep 029495e 8 | - Merge pull request [#285](https://github.com/ilovepixelart/ts-cache-mongoose/issues/285) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-90d3097017 d6714ad 9 | - Update dependencies: Bump the all-dependencies group with 4 updates a0025dc 10 | - Merge pull request [#284](https://github.com/ilovepixelart/ts-cache-mongoose/issues/284) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-91aaeed3ea d009999 11 | - Update dependencies: Bump the all-dependencies group with 5 updates 4efc2c3 12 | - Merge pull request [#283](https://github.com/ilovepixelart/ts-cache-mongoose/issues/283) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-50bf62f782 5faf95d 13 | - Update dependencies: Bump the all-dependencies group with 5 updates 874d0cb 14 | - Update changelog for v1.7.2 d36dabf 15 | 16 | --- 17 | 18 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.7.2...v1.7.3 19 | 20 | [Changes][v1.7.3] 21 | 22 | 23 | 24 | # [v1.7.2](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.7.2) - 2025-03-02 25 | 26 | - Type check de1b39a 27 | - Lock 2b15683 28 | - Dep 7d60d5a 29 | - Doc 184f1f8 30 | - Merge pull request [#282](https://github.com/ilovepixelart/ts-cache-mongoose/issues/282) from ilovepixelart/feature/esm 76ad448 31 | - ESM 939621a 32 | - Merge pull request [#281](https://github.com/ilovepixelart/ts-cache-mongoose/issues/281) from ilovepixelart/feature/dep d505bb6 33 | - Dep aab190e 34 | - Override c268240 35 | - Merge pull request [#278](https://github.com/ilovepixelart/ts-cache-mongoose/issues/278) from ilovepixelart/feature/pkgroll 472772d 36 | - Merge remote-tracking branch &[#39](https://github.com/ilovepixelart/ts-cache-mongoose/issues/39);origin/main&[#39](https://github.com/ilovepixelart/ts-cache-mongoose/issues/39); into feature/pkgroll 988d322 37 | - Merge pull request [#279](https://github.com/ilovepixelart/ts-cache-mongoose/issues/279) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-b484da3d92 d698f0b 38 | - Update dependencies: Bump the all-dependencies group with 5 updates 6f8eec0 39 | - Dep 36c82ec 40 | - Clean lock bb52056 41 | - Import 5ee7e30 42 | - Switch to pkgroll 49ebd60 43 | - Merge pull request [#277](https://github.com/ilovepixelart/ts-cache-mongoose/issues/277) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-840c07ccc3 c4b9c70 44 | - Update dependencies: Bump the all-dependencies group with 2 updates ea6ac5c 45 | - Merge pull request [#276](https://github.com/ilovepixelart/ts-cache-mongoose/issues/276) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-e2d4cef7ba a68ca84 46 | - Update dependencies: Bump the all-dependencies group with 6 updates 1f8d281 47 | - Merge pull request [#275](https://github.com/ilovepixelart/ts-cache-mongoose/issues/275) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-8fd2c74fee 90d1369 48 | - Update dependencies: Bump the all-dependencies group with 4 updates 3073331 49 | - Merge pull request [#274](https://github.com/ilovepixelart/ts-cache-mongoose/issues/274) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-10fca7b204 b8bc4d7 50 | - Update dependencies: Bump the all-dependencies group with 3 updates 3cee387 51 | - Update changelog for v1.7.1 c23e87d 52 | 53 | --- 54 | 55 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.7.1...v1.7.2 56 | 57 | [Changes][v1.7.2] 58 | 59 | 60 | 61 | # [v1.7.1](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.7.1) - 2025-01-19 62 | 63 | - Biome afbb41d 64 | - Remove unused bffab0d 65 | - Remove unused 16fbb85 66 | - Update changelog for v1.7.0 6b47229 67 | 68 | --- 69 | 70 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.7.0...v1.7.1 71 | 72 | [Changes][v1.7.1] 73 | 74 | 75 | 76 | # [v1.7.0](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.7.0) - 2025-01-19 77 | 78 | - Merge pull request [#273](https://github.com/ilovepixelart/ts-cache-mongoose/issues/273) from ilovepixelart/feature/move-all-types-to-one-file c169849 79 | - Sort imports dc45b11 80 | - Refactor import, export, test models 84508bf 81 | - Convert cache options to type 1a90719 82 | - Move all types to one file and renamed: - type IData -> CacheData - type ICacheOptions -> CacheOptions - type ICacheEngine -> CacheEngine - class CacheEngine -> Cache 8851b9a 83 | - Update changelog for v1.6.8 8d56584 84 | 85 | --- 86 | 87 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.8...v1.7.0 88 | 89 | [Changes][v1.7.0] 90 | 91 | 92 | 93 | # [v1.6.8](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.8) - 2025-01-19 94 | 95 | - Merge pull request [#272](https://github.com/ilovepixelart/ts-cache-mongoose/issues/272) from ilovepixelart/feature/better-types-for-ttl c61d9e1 96 | - Better types for TTL 85454ca 97 | - Merge pull request [#271](https://github.com/ilovepixelart/ts-cache-mongoose/issues/271) from ilovepixelart/dependabot-npm_and_yarn-all-dependencies-0bc38f1f4e cdb5fee 98 | - Update dependencies: Bump the all-dependencies group with 5 updates 5f78453 99 | - Dependabot a135c4e 100 | - Update settings f1e2765 101 | - Merge pull request [#270](https://github.com/ilovepixelart/ts-cache-mongoose/issues/270) from ilovepixelart/feature/update-action-and-dep 6f3f7d3 102 | - Action and Dep 3e003dc 103 | - Merge pull request [#267](https://github.com/ilovepixelart/ts-cache-mongoose/issues/267) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.9.3 bbbac03 104 | - Bump mongoose from 8.9.2 to 8.9.3 55bd4fa 105 | - Merge pull request [#268](https://github.com/ilovepixelart/ts-cache-mongoose/issues/268) from ilovepixelart/dependabot/npm_and_yarn/types/node-22.10.5 823c518 106 | - Merge pull request [#269](https://github.com/ilovepixelart/ts-cache-mongoose/issues/269) from ilovepixelart/dependabot/npm_and_yarn/mongodb-memory-server-10.1.3 14d27bc 107 | - Bump mongodb-memory-server from 10.1.2 to 10.1.3 0071236 108 | - Bump @types/node from 22.10.2 to 22.10.5 411c9ae 109 | - Merge pull request [#265](https://github.com/ilovepixelart/ts-cache-mongoose/issues/265) from ilovepixelart/dependabot/npm_and_yarn/ioredis-5.4.2 7facf90 110 | - Merge pull request [#266](https://github.com/ilovepixelart/ts-cache-mongoose/issues/266) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.9.2 9ee8e8d 111 | - Bump mongoose from 8.9.0 to 8.9.2 2db95e8 112 | - Bump ioredis from 5.4.1 to 5.4.2 85bec09 113 | - Merge pull request [#263](https://github.com/ilovepixelart/ts-cache-mongoose/issues/263) from ilovepixelart/dependabot/npm_and_yarn/types/node-22.10.2 562a9fe 114 | - Merge pull request [#264](https://github.com/ilovepixelart/ts-cache-mongoose/issues/264) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.9.0 5e99298 115 | - Bump mongoose from 8.8.4 to 8.9.0 817c968 116 | - Bump @types/node from 22.10.1 to 22.10.2 cd0b7f1 117 | - Merge pull request [#262](https://github.com/ilovepixelart/ts-cache-mongoose/issues/262) from ilovepixelart/dependabot/npm_and_yarn/bson-6.10.1 0fd4eac 118 | - Merge pull request [#261](https://github.com/ilovepixelart/ts-cache-mongoose/issues/261) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.8.4 8b30004 119 | - Bump bson from 6.10.0 to 6.10.1 dd15d78 120 | - Bump mongoose from 8.8.3 to 8.8.4 12a09cf 121 | - Update changelog for v1.6.7 3c2df91 122 | 123 | --- 124 | 125 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.7...v1.6.8 126 | 127 | [Changes][v1.6.8] 128 | 129 | 130 | 131 | # [v1.6.7](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.7) - 2024-12-03 132 | 133 | - Merge pull request [#260](https://github.com/ilovepixelart/ts-cache-mongoose/issues/260) from ilovepixelart/feature/dep ff760c0 134 | - Dep 93cda9f 135 | - Merge pull request [#259](https://github.com/ilovepixelart/ts-cache-mongoose/issues/259) from ilovepixelart/feature/dep f5a8ba2 136 | - Dep 62d36fd 137 | - Merge pull request [#256](https://github.com/ilovepixelart/ts-cache-mongoose/issues/256) from ilovepixelart/dependabot/npm_and_yarn/types/node-22.9.3 84e0e12 138 | - Merge pull request [#257](https://github.com/ilovepixelart/ts-cache-mongoose/issues/257) from ilovepixelart/dependabot/npm_and_yarn/bson-6.10.0 f3fe5e1 139 | - Merge pull request [#258](https://github.com/ilovepixelart/ts-cache-mongoose/issues/258) from ilovepixelart/dependabot/npm_and_yarn/typescript-5.7.2 fcb68dd 140 | - Bump typescript from 5.6.3 to 5.7.2 8042d70 141 | - Bump bson from 6.9.0 to 6.10.0 8194b74 142 | - Bump @types/node from 22.9.1 to 22.9.3 fd90046 143 | - File 2b810ac 144 | - Merge pull request [#255](https://github.com/ilovepixelart/ts-cache-mongoose/issues/255) from ilovepixelart/snyk-upgrade-0409fd4b367c28a88d5514ee927f3f98 f78af6b 145 | - fix: upgrade bson from 6.8.0 to 6.9.0 7e98dba 146 | - Merge pull request [#254](https://github.com/ilovepixelart/ts-cache-mongoose/issues/254) from ilovepixelart/feature/dep 9d3a6a4 147 | - Dep & Coverage 1fd9ab2 148 | - Merge pull request [#253](https://github.com/ilovepixelart/ts-cache-mongoose/issues/253) from ilovepixelart/feature/vitest f726974 149 | - Test 319b7c5 150 | - Merge pull request [#252](https://github.com/ilovepixelart/ts-cache-mongoose/issues/252) from ilovepixelart/feature/vitest 02d856c 151 | - npm 6c73895 152 | - Biome 0bf9013 153 | - Server create, destroy 7cfc5a0 154 | - Biome 989871b 155 | - Unify, fs fallback 44e650e 156 | - Types 7142164 157 | - Types 6576451 158 | - Biome cfb97ab 159 | - Refactor b27064e 160 | - Refactor fc1f2a6 161 | - toBe db01da3 162 | - Biome fix b5b88f4 163 | - Vitest ac72099 164 | - Doc 958d33a 165 | - Doc 7bd8e6d 166 | - Merge pull request [#251](https://github.com/ilovepixelart/ts-cache-mongoose/issues/251) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.8.1 c1f4e07 167 | - Merge pull request [#250](https://github.com/ilovepixelart/ts-cache-mongoose/issues/250) from ilovepixelart/dependabot/npm_and_yarn/types/node-22.9.0 d8d481a 168 | - Bump mongoose from 8.8.0 to 8.8.1 3095ccd 169 | - Bump @types/node from 22.8.7 to 22.9.0 6a7f71f 170 | - Merge pull request [#248](https://github.com/ilovepixelart/ts-cache-mongoose/issues/248) from ilovepixelart/dependabot/npm_and_yarn/types/node-22.8.7 4ef2107 171 | - Merge pull request [#249](https://github.com/ilovepixelart/ts-cache-mongoose/issues/249) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.8.0 6dd5b2f 172 | - Bump mongoose from 8.7.3 to 8.8.0 1c67e93 173 | - Bump @types/node from 22.8.1 to 22.8.7 7624065 174 | - Update changelog for v1.6.6 2382ddd 175 | 176 | --- 177 | 178 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.6...v1.6.7 179 | 180 | [Changes][v1.6.7] 181 | 182 | 183 | 184 | # [v1.6.6](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.6) - 2024-10-26 185 | 186 | - Merge pull request [#246](https://github.com/ilovepixelart/ts-cache-mongoose/issues/246) from ilovepixelart/feature/dep 37a0af2 187 | - Dep d8c0a00 188 | - Merge pull request [#243](https://github.com/ilovepixelart/ts-cache-mongoose/issues/243) from ilovepixelart/dependabot/npm_and_yarn/biomejs/biome-1.9.4 8ef3716 189 | - Merge pull request [#244](https://github.com/ilovepixelart/ts-cache-mongoose/issues/244) from ilovepixelart/dependabot/npm_and_yarn/types/node-22.7.7 89805b9 190 | - Bump @types/node from 22.7.5 to 22.7.7 dd5d7bf 191 | - Bump @biomejs/biome from 1.9.3 to 1.9.4 e19eedb 192 | - Merge pull request [#242](https://github.com/ilovepixelart/ts-cache-mongoose/issues/242) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.7.2 e264412 193 | - Bump mongoose from 8.7.1 to 8.7.2 c9e36ed 194 | - Merge pull request [#239](https://github.com/ilovepixelart/ts-cache-mongoose/issues/239) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.7.1 3e93949 195 | - Merge pull request [#241](https://github.com/ilovepixelart/ts-cache-mongoose/issues/241) from ilovepixelart/dependabot/npm_and_yarn/types/node-22.7.5 00729a3 196 | - Merge pull request [#240](https://github.com/ilovepixelart/ts-cache-mongoose/issues/240) from ilovepixelart/dependabot/npm_and_yarn/typescript-5.6.3 9dd411e 197 | - Bump @types/node from 22.7.4 to 22.7.5 1cf6f88 198 | - Bump typescript from 5.6.2 to 5.6.3 693b60b 199 | - Bump mongoose from 8.7.0 to 8.7.1 b31da83 200 | - Merge pull request [#238](https://github.com/ilovepixelart/ts-cache-mongoose/issues/238) from ilovepixelart/feature/switch-to-biome 8deb6a1 201 | - Command f761f98 202 | - Simplify biome config to required rules ecf033b 203 | - Merge pull request [#237](https://github.com/ilovepixelart/ts-cache-mongoose/issues/237) from ilovepixelart/feature/switch-to-biome 24f7b4f 204 | - Switch to biome d1bf328 205 | - Cleanup 66d2087 206 | - Test for debug logs in cache 4a98e25 207 | - Update changelog for v1.6.5 f74152c 208 | 209 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.5...v1.6.6 210 | 211 | [Changes][v1.6.6] 212 | 213 | 214 | 215 | # [v1.6.5](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.5) - 2024-09-21 216 | 217 | - Merge pull request [#231](https://github.com/ilovepixelart/ts-cache-mongoose/issues/231) from ilovepixelart/feature/dep 1886957 218 | - Dep a1937c6 219 | - Update changelog for v1.6.4 0bc67f7 220 | 221 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.4...v1.6.5 222 | 223 | [Changes][v1.6.5] 224 | 225 | 226 | 227 | # [v1.6.4](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.4) - 2024-08-01 228 | 229 | - Merge pull request [#224](https://github.com/ilovepixelart/ts-cache-mongoose/issues/224) from ilovepixelart/feature/dep dc8b560 230 | - Dep 900009d 231 | - Update changelog for v1.6.3 f3cc884 232 | 233 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.3...v1.6.4 234 | 235 | [Changes][v1.6.4] 236 | 237 | 238 | 239 | # [v1.6.3](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.3) - 2024-07-06 240 | 241 | - Merge pull request [#214](https://github.com/ilovepixelart/ts-cache-mongoose/issues/214) from ilovepixelart/feature/dep d34c8fc 242 | - Dep ed45048 243 | - Doc 42649db 244 | - Update changelog for v1.6.2 a25949e 245 | 246 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.2...v1.6.3 247 | 248 | [Changes][v1.6.3] 249 | 250 | 251 | 252 | # [v1.6.2](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.2) - 2024-06-15 253 | 254 | - Merge pull request [#208](https://github.com/ilovepixelart/ts-cache-mongoose/issues/208) from ilovepixelart/feature/dep 81bbd44 255 | - Doc 6fad2db 256 | - Dep 75a5b37 257 | - Merge pull request [#207](https://github.com/ilovepixelart/ts-cache-mongoose/issues/207) from ilovepixelart/feature/upgrade-actions 65e97a7 258 | - Upgrade actions 49c732b 259 | - Merge pull request [#206](https://github.com/ilovepixelart/ts-cache-mongoose/issues/206) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/parser-7.11.0 8781ae1 260 | - Bump @typescript-eslint/parser from 7.9.0 to 7.11.0 a299c97 261 | - Merge pull request [#202](https://github.com/ilovepixelart/ts-cache-mongoose/issues/202) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-7.11.0 24183e9 262 | - Merge pull request [#205](https://github.com/ilovepixelart/ts-cache-mongoose/issues/205) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.4.1 dad5623 263 | - Merge pull request [#204](https://github.com/ilovepixelart/ts-cache-mongoose/issues/204) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.13.0 56dce3d 264 | - Bump mongoose from 8.4.0 to 8.4.1 ed0fc49 265 | - Bump @types/node from 20.12.7 to 20.13.0 3614288 266 | - Bump @typescript-eslint/eslint-plugin from 7.9.0 to 7.11.0 23417ad 267 | - Update changelog for v1.6.1 66bec2f 268 | 269 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.1...v1.6.2 270 | 271 | [Changes][v1.6.2] 272 | 273 | 274 | 275 | # [v1.6.1](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.1) - 2024-05-18 276 | 277 | - Merge pull request [#201](https://github.com/ilovepixelart/ts-cache-mongoose/issues/201) from ilovepixelart/feature/dep 708483f 278 | - Dep 331728b 279 | - Update changelog for v1.6.0 fd97423 280 | 281 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.0...v1.6.1 282 | 283 | [Changes][v1.6.1] 284 | 285 | 286 | 287 | # [v1.6.0](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.6.0) - 2024-05-10 288 | 289 | - Merge pull request [#200](https://github.com/ilovepixelart/ts-cache-mongoose/issues/200) from ilovepixelart/feature/test de2f1d1 290 | - Test for populated 766d766 291 | - Merge pull request [#199](https://github.com/ilovepixelart/ts-cache-mongoose/issues/199) from ilovepixelart/feature/tests 9a28de6 292 | - More tests 81dfdd8 293 | - Merge pull request [#198](https://github.com/ilovepixelart/ts-cache-mongoose/issues/198) from ilovepixelart/feature/serializer 12ea7f8 294 | - Import order 42dd7a4 295 | - Fallback to db in case serialization fails 5e95320 296 | - Node 22 1f9798c 297 | - Dep aba8c01 298 | - Types 26e8a4b 299 | - Dep 72e2ae5 300 | - Latest major versions d171775 301 | - Try parallel again 239419d 302 | - Dep 62691c0 303 | - Sequential + bson versions 4041200 304 | - Pipeline ffc9994 305 | - Check for all versions a9486cb 306 | - Dep 5564204 307 | - Dep c43e7e2 308 | - Lint 2ff3b04 309 | - Corner case for mongoose 6 a3341e4 310 | - Dep 5215a13 311 | - Dep d794831 312 | - Serializer b146f59 313 | - Merge pull request [#193](https://github.com/ilovepixelart/ts-cache-mongoose/issues/193) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-7.8.0 2c29a03 314 | - Bump @typescript-eslint/eslint-plugin from 7.7.0 to 7.8.0 c369e18 315 | - Merge pull request [#194](https://github.com/ilovepixelart/ts-cache-mongoose/issues/194) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/parser-7.8.0 dbc3837 316 | - Merge pull request [#195](https://github.com/ilovepixelart/ts-cache-mongoose/issues/195) from ilovepixelart/dependabot/npm_and_yarn/eslint-plugin-jest-28.3.0 1e342fd 317 | - Merge pull request [#196](https://github.com/ilovepixelart/ts-cache-mongoose/issues/196) from ilovepixelart/dependabot/npm_and_yarn/swc/helpers-0.5.11 aefc0d3 318 | - Merge pull request [#197](https://github.com/ilovepixelart/ts-cache-mongoose/issues/197) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.12.7 934d890 319 | - Bump @types/node from 20.12.2 to 20.12.7 25a8db9 320 | - Bump @swc/helpers from 0.5.10 to 0.5.11 e62b766 321 | - Bump eslint-plugin-jest from 28.2.0 to 28.3.0 2299342 322 | - Bump @typescript-eslint/parser from 7.7.0 to 7.8.0 7723e23 323 | - Merge pull request [#192](https://github.com/ilovepixelart/ts-cache-mongoose/issues/192) from ilovepixelart/feature/dep 13eed72 324 | - Dep 44a924e 325 | - Update changelog for v1.5.0 aa9177b 326 | 327 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.5.0...v1.6.0 328 | 329 | [Changes][v1.6.0] 330 | 331 | 332 | 333 | # [v1.5.0](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.5.0) - 2024-04-01 334 | 335 | - Eslint 6f4aea2 336 | - Merge pull request [#183](https://github.com/ilovepixelart/ts-cache-mongoose/issues/183) from PfisterFactor/main e1da1f1 337 | - fix: lint again 214fb35 338 | - Merge pull request [#189](https://github.com/ilovepixelart/ts-cache-mongoose/issues/189) from ilovepixelart/dependabot/npm_and_yarn/eslint-plugin-sonarjs-0.25.0 7a9fa42 339 | - Bump eslint-plugin-sonarjs from 0.24.0 to 0.25.0 a454088 340 | - Merge pull request [#186](https://github.com/ilovepixelart/ts-cache-mongoose/issues/186) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.2.4 919f735 341 | - Merge pull request [#187](https://github.com/ilovepixelart/ts-cache-mongoose/issues/187) from ilovepixelart/dependabot/npm_and_yarn/swc/cli-0.3.12 6da6a7d 342 | - Merge pull request [#188](https://github.com/ilovepixelart/ts-cache-mongoose/issues/188) from ilovepixelart/dependabot/npm_and_yarn/swc/helpers-0.5.8 481b6d4 343 | - Merge pull request [#190](https://github.com/ilovepixelart/ts-cache-mongoose/issues/190) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.12.2 f84e1b6 344 | - Bump @types/node from 20.11.24 to 20.12.2 7792d7f 345 | - Bump @swc/helpers from 0.5.7 to 0.5.8 14d4306 346 | - Bump @swc/cli from 0.3.10 to 0.3.12 fadec48 347 | - Bump mongoose from 8.2.3 to 8.2.4 d2c9f49 348 | - fix: linting 414b4a5 349 | - Merge pull request [#185](https://github.com/ilovepixelart/ts-cache-mongoose/issues/185) from ilovepixelart/feature/dep 1a1a1f3 350 | - Dep 9282287 351 | - Merge pull request [#184](https://github.com/ilovepixelart/ts-cache-mongoose/issues/184) from ilovepixelart/snyk-upgrade-0cc3c149099921abc857d6f2207ebc4d ec6382e 352 | - fix: upgrade mongoose from 8.2.0 to 8.2.1 3ed60f8 353 | - fix: boolean check 49cf22e 354 | - feat: add debug logging 59bd5df 355 | - Update changelog for v1.4.7 2ebcef7 356 | 357 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.7...v1.5.0 358 | 359 | [Changes][v1.5.0] 360 | 361 | 362 | 363 | # [v1.4.7](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.4.7) - 2024-03-14 364 | 365 | - Merge pull request [#181](https://github.com/ilovepixelart/ts-cache-mongoose/issues/181) from ilovepixelart/feature/dep 9b6afae 366 | - tsconfig 196a371 367 | - Dep edfe4e3 368 | - Merge pull request [#180](https://github.com/ilovepixelart/ts-cache-mongoose/issues/180) from ilovepixelart/feature/lint 531edee 369 | - Lint eb152d4 370 | - Merge pull request [#179](https://github.com/ilovepixelart/ts-cache-mongoose/issues/179) from ilovepixelart/dependabot/npm_and_yarn/swc/cli-0.3.10 4ad38ae 371 | - Merge pull request [#177](https://github.com/ilovepixelart/ts-cache-mongoose/issues/177) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/parser-7.1.0 6cde986 372 | - Bump @typescript-eslint/parser from 7.0.2 to 7.1.0 0eb587c 373 | - Merge pull request [#176](https://github.com/ilovepixelart/ts-cache-mongoose/issues/176) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.11.24 639fba0 374 | - Merge pull request [#178](https://github.com/ilovepixelart/ts-cache-mongoose/issues/178) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-7.1.0 090792f 375 | - Bump @swc/cli from 0.3.9 to 0.3.10 4ff420b 376 | - Bump @typescript-eslint/eslint-plugin from 7.0.2 to 7.1.0 dec1024 377 | - Bump @types/node from 20.11.20 to 20.11.24 80a8ad8 378 | - Merge pull request [#173](https://github.com/ilovepixelart/ts-cache-mongoose/issues/173) from ekamahuja/private-ecma-# e846475 379 | - Merge remote-tracking branch &[#39](https://github.com/ilovepixelart/ts-cache-mongoose/issues/39);upstream/main&[#39](https://github.com/ilovepixelart/ts-cache-mongoose/issues/39); into private-ecma-# bc82eb9 380 | - Skip sonar for forked PRs 9d7989f 381 | - Merge pull request [#175](https://github.com/ilovepixelart/ts-cache-mongoose/issues/175) from ekamahuja/installation-bun-readme 18ba561 382 | - Added &[#39](https://github.com/ilovepixelart/ts-cache-mongoose/issues/39);bun&[#39](https://github.com/ilovepixelart/ts-cache-mongoose/issues/39); commands to installation instructions in README.md 1331853 383 | - Refactor: Replace private keyword with ECMAScript private fields e26d905 384 | - Update changelog for v1.4.6 a9f621c 385 | 386 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.6...v1.4.7 387 | 388 | [Changes][v1.4.7] 389 | 390 | 391 | 392 | # [v1.4.6](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.4.6) - 2024-02-24 393 | 394 | - Lock a389447 395 | - Merge pull request [#169](https://github.com/ilovepixelart/ts-cache-mongoose/issues/169) from ilovepixelart/dependabot/npm_and_yarn/mongoose-8.2.0 752171b 396 | - Merge pull request [#170](https://github.com/ilovepixelart/ts-cache-mongoose/issues/170) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.11.20 a1d3f34 397 | - Merge pull request [#171](https://github.com/ilovepixelart/ts-cache-mongoose/issues/171) from ilovepixelart/dependabot/npm_and_yarn/eslint-8.57.0 4a101a8 398 | - Bump eslint from 8.56.0 to 8.57.0 ae10a2c 399 | - Bump @types/node from 20.8.10 to 20.11.20 d63732c 400 | - Bump mongoose from 8.0.0 to 8.2.0 b82efd0 401 | - Dependabot ignore 0181902 402 | - Merge pull request [#168](https://github.com/ilovepixelart/ts-cache-mongoose/issues/168) from ilovepixelart/feature/dep a3d1be1 403 | - Merge pull request [#167](https://github.com/ilovepixelart/ts-cache-mongoose/issues/167) from ekamahuja/memor-engine-map 8f50596 404 | - Remove eslint delete method memory engine comment cb72814 405 | - Enhancement: Upgrade cache to use Map for improved performance e3df1e9 406 | - Dep da387c0 407 | - Update changelog for v1.4.5 538865a 408 | - Merge pull request [#163](https://github.com/ilovepixelart/ts-cache-mongoose/issues/163) from ilovepixelart/feature/dep 9f37105 409 | - Dep 3916a8f 410 | - Merge pull request [#155](https://github.com/ilovepixelart/ts-cache-mongoose/issues/155) from ilovepixelart/feature/dep 13d99f3 411 | - Dep 03656ba 412 | - Update changelog for "v1.4.5" 13a3e8e 413 | 414 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.5...v1.4.6 415 | 416 | [Changes][v1.4.6] 417 | 418 | 419 | 420 | # [v1.4.5](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.4.5) - 2024-01-20 421 | 422 | - Merge pull request [#154](https://github.com/ilovepixelart/ts-cache-mongoose/issues/154) from ilovepixelart/feature/dep 450ff32 423 | - Dep e49aa31 424 | - Update changelog for "v1.4.4" da6b190 425 | 426 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.4...v1.4.5 427 | 428 | [Changes][v1.4.5] 429 | 430 | 431 | 432 | # [v1.4.4](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.4.4) - 2024-01-09 433 | 434 | - Merge pull request [#152](https://github.com/ilovepixelart/ts-cache-mongoose/issues/152) from ilovepixelart/feature/esm-bash-and-dep 5ea3ecb 435 | - Audit e98d515 436 | - ESM bash and Dep d6851db 437 | - Update changelog for "v1.4.3" ca0e4d2 438 | 439 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.3...v1.4.4 440 | 441 | [Changes][v1.4.4] 442 | 443 | 444 | 445 | # [v1.4.3](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.4.3) - 2023-12-06 446 | 447 | - Doc ae2fee9 448 | - Merge pull request [#143](https://github.com/ilovepixelart/ts-cache-mongoose/issues/143) from ilovepixelart/feature/esm-support 9cdaa4a 449 | - Doc ee606bb 450 | - ESM support 1f2c00f 451 | - Settings 70858d6 452 | - System files ignore 41bcd1c 453 | - Delimiter style for interfaces 68003aa 454 | - Update changelog for "v1.4.2" 06cacc7 455 | 456 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.2...v1.4.3 457 | 458 | [Changes][v1.4.3] 459 | 460 | 461 | 462 | # [v1.4.2](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.4.2) - 2023-11-28 463 | 464 | - Merge pull request [#135](https://github.com/ilovepixelart/ts-cache-mongoose/issues/135) from ilovepixelart/feature/dep 9823556 465 | - Dep 73c0524 466 | - Update changelog for "v1.4.1" 67a8c78 467 | 468 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.1...v1.4.2 469 | 470 | [Changes][v1.4.2] 471 | 472 | 473 | 474 | # [v1.4.1](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.4.1) - 2023-11-15 475 | 476 | - Merge pull request [#134](https://github.com/ilovepixelart/ts-cache-mongoose/issues/134) from ilovepixelart/feature/custom-key-for-aggregate bd4cd95 477 | - Enable custom cache key for aggregate ff4151c 478 | - Doc b6300b6 479 | - Doc dad993d 480 | - Update changelog for "v1.4.0" 8fe292c 481 | 482 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.0...v1.4.1 483 | 484 | [Changes][v1.4.1] 485 | 486 | 487 | 488 | # [v1.4.0](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.4.0) - 2023-11-12 489 | 490 | - Merge pull request [#131](https://github.com/ilovepixelart/ts-cache-mongoose/issues/131) from ilovepixelart/feature/tsconfig-eslint df15116 491 | - Dep 9a8e813 492 | - Engines 66b73a8 493 | - Tsconfig strictest rules 64923f0 494 | - Update changelog for "v1.3.0" a215539 495 | 496 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.3.0...v1.4.0 497 | 498 | [Changes][v1.4.0] 499 | 500 | 501 | 502 | # [v1.3.0](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.3.0) - 2023-11-06 503 | 504 | - Merge pull request [#130](https://github.com/ilovepixelart/ts-cache-mongoose/issues/130) from ilovepixelart/feature/mongoose-8 682d868 505 | - Latest 7a5bcd8 506 | - Doc dd80f0b 507 | - Mongoose 8 07eb6e0 508 | - Merge pull request [#129](https://github.com/ilovepixelart/ts-cache-mongoose/issues/129) from ilovepixelart/feature/dep 5ffeae7 509 | - Fixed versions 04b67c6 510 | - Dep b030219 511 | - Merge pull request [#123](https://github.com/ilovepixelart/ts-cache-mongoose/issues/123) from ilovepixelart/dependabot/npm_and_yarn/babel/traverse-7.23.2 5201ef3 512 | - Bump @babel/traverse from 7.22.17 to 7.23.2 8361552 513 | - Update changelog for "v1.2.1" fd583ee 514 | 515 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.2.1...v1.3.0 516 | 517 | [Changes][v1.3.0] 518 | 519 | 520 | 521 | # [v1.2.1](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.2.1) - 2023-10-16 522 | 523 | - Merge pull request [#122](https://github.com/ilovepixelart/ts-cache-mongoose/issues/122) from ilovepixelart/feature/dep 43c9973 524 | - Dep fc808db 525 | - Merge pull request [#121](https://github.com/ilovepixelart/ts-cache-mongoose/issues/121) from ilovepixelart/feature/object-id-detection 4e6c111 526 | - Certain strings get erroneously deserialised to ObjectID b045283 527 | - Update changelog for "v1.2.0" b65a2fa 528 | 529 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.2.0...v1.2.1 530 | 531 | [Changes][v1.2.1] 532 | 533 | 534 | 535 | # [v1.2.0](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.2.0) - 2023-10-02 536 | 537 | - Merge pull request [#118](https://github.com/ilovepixelart/ts-cache-mongoose/issues/118) from ilovepixelart/feature/dep 4541bc3 538 | - Dep e805e09 539 | - Merge pull request [#110](https://github.com/ilovepixelart/ts-cache-mongoose/issues/110) from ilovepixelart/feature/dep b132dcb 540 | - Monthly 992e7b6 541 | - Dep 7b134aa 542 | - Update changelog for "v1.1.2" 9290d14 543 | 544 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.1.2...v1.2.0 545 | 546 | [Changes][v1.2.0] 547 | 548 | 549 | 550 | # [v1.1.2](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.1.2) - 2023-09-01 551 | 552 | - Merge pull request [#102](https://github.com/ilovepixelart/ts-cache-mongoose/issues/102) from ilovepixelart/feature/dep 916df05 553 | - NodeNext 66dbe4d 554 | - Dep df4e305 555 | - Merge pull request [#94](https://github.com/ilovepixelart/ts-cache-mongoose/issues/94) from pilot22/main 6a9671f 556 | - Update README.md 440d924 557 | - Merge pull request [#93](https://github.com/ilovepixelart/ts-cache-mongoose/issues/93) from ilovepixelart/feature/dep e17fafb 558 | - Dep 1c7a4b2 559 | - Merge pull request [#91](https://github.com/ilovepixelart/ts-cache-mongoose/issues/91) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-6.4.0 2156674 560 | - Merge pull request [#88](https://github.com/ilovepixelart/ts-cache-mongoose/issues/88) from ilovepixelart/dependabot/npm_and_yarn/swc/core-1.3.78 85a356f 561 | - Bump @typescript-eslint/eslint-plugin from 6.3.0 to 6.4.0 e0511e1 562 | - Bump @swc/core from 1.3.76 to 1.3.78 46c70ab 563 | - Update changelog for "v1.1.1" 4cc4ddf 564 | 565 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.1.1...v1.1.2 566 | 567 | [Changes][v1.1.2] 568 | 569 | 570 | 571 | # [v1.1.1](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.1.1) - 2023-08-14 572 | 573 | - Merge pull request [#85](https://github.com/ilovepixelart/ts-cache-mongoose/issues/85) from ilovepixelart/feature/dep 59009f3 574 | - Dep - Update dependencies 8999ac0 575 | - Update changelog for "v1.1.0" 6fa4781 576 | 577 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.1.0...v1.1.1 578 | 579 | [Changes][v1.1.1] 580 | 581 | 582 | 583 | # [v1.1.0](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.1.0) - 2023-08-05 584 | 585 | - Merge pull request [#79](https://github.com/ilovepixelart/ts-cache-mongoose/issues/79) from ilovepixelart/feature/dep 189e3f4 586 | - Dep + new version of swc - Remove env use target in .swcrc 47d040e 587 | - Merge pull request [#76](https://github.com/ilovepixelart/ts-cache-mongoose/issues/76) from ilovepixelart/dependabot/npm_and_yarn/eslint-8.46.0 d6d11d1 588 | - Merge pull request [#77](https://github.com/ilovepixelart/ts-cache-mongoose/issues/77) from ilovepixelart/dependabot/npm_and_yarn/jest-29.6.2 45f271e 589 | - Bump jest from 29.6.1 to 29.6.2 ba66284 590 | - Bump eslint from 8.45.0 to 8.46.0 ae317b3 591 | - Update changelog for "v1.0.9" 73394a4 592 | 593 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.9...v1.1.0 594 | 595 | [Changes][v1.1.0] 596 | 597 | 598 | 599 | # [v1.0.9](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.9) - 2023-07-24 600 | 601 | - Merge pull request [#72](https://github.com/ilovepixelart/ts-cache-mongoose/issues/72) from ilovepixelart/feature/dep c39c013 602 | - Dep 4350ab8 603 | - Merge pull request [#69](https://github.com/ilovepixelart/ts-cache-mongoose/issues/69) from ilovepixelart/feature/lint de1f96d 604 | - Eslint strict 67dda77 605 | - Update changelog for "v1.0.8" 56b6c8a 606 | 607 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.8...v1.0.9 608 | 609 | [Changes][v1.0.9] 610 | 611 | 612 | 613 | # [v1.0.8](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.8) - 2023-07-20 614 | 615 | - Merge pull request [#68](https://github.com/ilovepixelart/ts-cache-mongoose/issues/68) from ilovepixelart/feature/dep f4fec16 616 | - Dep a852dcf 617 | - Merge pull request [#67](https://github.com/ilovepixelart/ts-cache-mongoose/issues/67) from ilovepixelart/feature/custom-key-empty-string 27204ec 618 | - Check != null 9fd1486 619 | - Type of string 5a4fb3e 620 | - Test description d790d68 621 | - Custom key empty string 79880ed 622 | - Update changelog for "v1.0.7" 8d4ce3e 623 | 624 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.7...v1.0.8 625 | 626 | [Changes][v1.0.8] 627 | 628 | 629 | 630 | # [v1.0.7](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.7) - 2023-07-16 631 | 632 | - Merge pull request [#63](https://github.com/ilovepixelart/ts-cache-mongoose/issues/63) from ilovepixelart/feature/dep daf6776 633 | - Ts node aa08a17 634 | - Dep 5fb1efb 635 | - Merge pull request [#58](https://github.com/ilovepixelart/ts-cache-mongoose/issues/58) from ilovepixelart/snyk-upgrade-8a29cddcb70f40bb2243efa11c83881a b784814 636 | - fix: upgrade mongoose from 6.11.1 to 6.11.2 fe5468a 637 | - Update changelog for "v1.0.6" 1c92b67 638 | 639 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.6...v1.0.7 640 | 641 | [Changes][v1.0.7] 642 | 643 | 644 | 645 | # [v1.0.6](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.6) - 2023-07-04 646 | 647 | - Merge pull request [#57](https://github.com/ilovepixelart/ts-cache-mongoose/issues/57) from ilovepixelart/feature/dep 06e9cb2 648 | - Dep 3d3876f 649 | - Merge pull request [#55](https://github.com/ilovepixelart/ts-cache-mongoose/issues/55) from ilovepixelart/dependabot/npm_and_yarn/eslint-8.44.0 edb1de4 650 | - Bump eslint from 8.43.0 to 8.44.0 183a8bc 651 | - Merge pull request [#52](https://github.com/ilovepixelart/ts-cache-mongoose/issues/52) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.3.3 a885149 652 | - Merge pull request [#54](https://github.com/ilovepixelart/ts-cache-mongoose/issues/54) from ilovepixelart/dependabot/npm_and_yarn/eslint-plugin-n-16.0.1 0df2358 653 | - Merge pull request [#56](https://github.com/ilovepixelart/ts-cache-mongoose/issues/56) from ilovepixelart/dependabot/npm_and_yarn/swc/core-1.3.67 062be47 654 | - Bump @swc/core from 1.3.66 to 1.3.67 6a01da3 655 | - Bump eslint-plugin-n from 16.0.0 to 16.0.1 51d0445 656 | - Bump @types/node from 20.3.1 to 20.3.3 d48f584 657 | - Merge pull request [#51](https://github.com/ilovepixelart/ts-cache-mongoose/issues/51) from ilovepixelart/snyk-upgrade-28ddc4ad3cc15b5ccaf3efbf4c8efe08 12f1c00 658 | - fix: upgrade mongoose from 6.10.5 to 6.11.1 e94b5cc 659 | - Merge pull request [#50](https://github.com/ilovepixelart/ts-cache-mongoose/issues/50) from ilovepixelart/dependabot/npm_and_yarn/fast-xml-parser-and-aws-sdk/credential-providers-4.2.5 f94d51d 660 | - Merge pull request [#46](https://github.com/ilovepixelart/ts-cache-mongoose/issues/46) from ilovepixelart/dependabot/npm_and_yarn/swc/core-1.3.66 d25909e 661 | - Merge pull request [#44](https://github.com/ilovepixelart/ts-cache-mongoose/issues/44) from ilovepixelart/dependabot/npm_and_yarn/eslint-8.43.0 730dc1b 662 | - Merge pull request [#49](https://github.com/ilovepixelart/ts-cache-mongoose/issues/49) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-5.60.1 9918498 663 | - Bump fast-xml-parser and @aws-sdk/credential-providers 565a529 664 | - Bump @typescript-eslint/eslint-plugin from 5.59.11 to 5.60.1 f360866 665 | - Bump eslint from 8.42.0 to 8.43.0 e83cedb 666 | - Merge pull request [#48](https://github.com/ilovepixelart/ts-cache-mongoose/issues/48) from ilovepixelart/dependabot/npm_and_yarn/eslint-plugin-jest-27.2.2 4a11f94 667 | - Bump eslint-plugin-jest from 27.2.1 to 27.2.2 53e086d 668 | - Bump @swc/core from 1.3.62 to 1.3.66 dedaba1 669 | - Merge pull request [#41](https://github.com/ilovepixelart/ts-cache-mongoose/issues/41) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/parser-5.59.11 1530df4 670 | - Merge pull request [#40](https://github.com/ilovepixelart/ts-cache-mongoose/issues/40) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.3.1 0314799 671 | - Bump @typescript-eslint/parser from 5.59.9 to 5.59.11 a7aa637 672 | - Bump @types/node from 20.2.5 to 20.3.1 f70db9f 673 | - Merge pull request [#43](https://github.com/ilovepixelart/ts-cache-mongoose/issues/43) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-5.59.11 dcab8c5 674 | - Bump @typescript-eslint/eslint-plugin from 5.59.9 to 5.59.11 e3b41f6 675 | - Update changelog for "v1.0.5" 427b286 676 | 677 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.5...v1.0.6 678 | 679 | [Changes][v1.0.6] 680 | 681 | 682 | 683 | # [v1.0.5](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.5) - 2023-06-09 684 | 685 | - Merge pull request [#36](https://github.com/ilovepixelart/ts-cache-mongoose/issues/36) from ilovepixelart/feature/dep 723a84b 686 | - Sonar cloud latest version, settings for vscode fdffdf1 687 | - Dep 4d68f53 688 | - Merge pull request [#35](https://github.com/ilovepixelart/ts-cache-mongoose/issues/35) from ilovepixelart/feature/dep 5cc9e34 689 | - Dep eca2a0a 690 | - Merge pull request [#34](https://github.com/ilovepixelart/ts-cache-mongoose/issues/34) from ilovepixelart/dependabot/npm_and_yarn/typescript-5.1.3 1947bb2 691 | - Bump typescript from 5.0.4 to 5.1.3 2a337d6 692 | - Merge pull request [#33](https://github.com/ilovepixelart/ts-cache-mongoose/issues/33) from ilovepixelart/dependabot/npm_and_yarn/swc/core-1.3.62 990a7a5 693 | - Merge pull request [#31](https://github.com/ilovepixelart/ts-cache-mongoose/issues/31) from ilovepixelart/dependabot/npm_and_yarn/types/jest-29.5.2 25ced59 694 | - Merge pull request [#30](https://github.com/ilovepixelart/ts-cache-mongoose/issues/30) from ilovepixelart/dependabot/npm_and_yarn/eslint-8.42.0 f7c9eda 695 | - Bump @swc/core from 1.3.61 to 1.3.62 be64cb2 696 | - Bump @types/jest from 29.5.1 to 29.5.2 5e4001a 697 | - Bump eslint from 8.41.0 to 8.42.0 4900965 698 | - Merge pull request [#29](https://github.com/ilovepixelart/ts-cache-mongoose/issues/29) from ilovepixelart/feature/dep 124df0e 699 | - Dep 9b1b0b9 700 | - Merge pull request [#24](https://github.com/ilovepixelart/ts-cache-mongoose/issues/24) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.2.5 bb9388c 701 | - Merge pull request [#23](https://github.com/ilovepixelart/ts-cache-mongoose/issues/23) from ilovepixelart/dependabot/npm_and_yarn/swc/core-1.3.60 008f11c 702 | - Merge pull request [#22](https://github.com/ilovepixelart/ts-cache-mongoose/issues/22) from ilovepixelart/dependabot/npm_and_yarn/typescript-eslint/parser-5.59.7 6d86a7d 703 | - Bump @types/node from 20.2.3 to 20.2.5 000a98b 704 | - Bump @swc/core from 1.3.59 to 1.3.60 f3d3509 705 | - Bump @typescript-eslint/parser from 5.59.6 to 5.59.7 f62a530 706 | - Merge pull request [#21](https://github.com/ilovepixelart/ts-cache-mongoose/issues/21) from ilovepixelart/dependabot/npm_and_yarn/eslint-8.41.0 bdffee1 707 | - Merge pull request [#20](https://github.com/ilovepixelart/ts-cache-mongoose/issues/20) from ilovepixelart/dependabot/npm_and_yarn/swc/core-1.3.59 02e51e1 708 | - Merge pull request [#18](https://github.com/ilovepixelart/ts-cache-mongoose/issues/18) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.2.3 b3da66e 709 | - Bump eslint from 8.40.0 to 8.41.0 01e08b5 710 | - Bump @swc/core from 1.3.58 to 1.3.59 0bc14d7 711 | - Bump @types/node from 20.1.7 to 20.2.3 dacefb5 712 | - Update changelog for "v1.0.4" 9fadca3 713 | 714 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.4...v1.0.5 715 | 716 | [Changes][v1.0.5] 717 | 718 | 719 | 720 | # [v1.0.4](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.4) - 2023-05-17 721 | 722 | - Merge pull request [#17](https://github.com/ilovepixelart/ts-cache-mongoose/issues/17) from ilovepixelart/feature/dep c13843f 723 | - Dep 370436d 724 | - Merge pull request [#15](https://github.com/ilovepixelart/ts-cache-mongoose/issues/15) from ilovepixelart/dependabot/npm_and_yarn/swc/core-1.3.58 c405297 725 | - Merge pull request [#16](https://github.com/ilovepixelart/ts-cache-mongoose/issues/16) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.1.4 a56a146 726 | - Bump @types/node from 20.1.3 to 20.1.4 051256c 727 | - Bump @swc/core from 1.3.57 to 1.3.58 96ba39c 728 | - Merge pull request [#13](https://github.com/ilovepixelart/ts-cache-mongoose/issues/13) from ilovepixelart/feature/dep 97f21a6 729 | - Dep 186fb1b 730 | - Merge pull request [#12](https://github.com/ilovepixelart/ts-cache-mongoose/issues/12) from ilovepixelart/dependabot/npm_and_yarn/types/node-20.1.0 e819e8f 731 | - Bump @types/node from 18.16.3 to 20.1.0 ce3c4d9 732 | - Merge pull request [#11](https://github.com/ilovepixelart/ts-cache-mongoose/issues/11) from ilovepixelart/feature/dep 24363c5 733 | - Dep eb57f13 734 | - Update changelog for "v1.0.3" 343521a 735 | 736 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.3...v1.0.4 737 | 738 | [Changes][v1.0.4] 739 | 740 | 741 | 742 | # [v1.0.3](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.3) - 2023-05-03 743 | 744 | ``` 745 | - Type for simplicity 746 | - Lean with hydration of dates and ids 747 | - Distinct hydration of dates and ids 748 | - Apply args for callbacks 749 | - Some tests to cover above 750 | ``` 751 | 752 | - Doc 6b5d1c7 753 | - Merge pull request [#10](https://github.com/ilovepixelart/ts-cache-mongoose/issues/10) from ilovepixelart/feature/apply 6184e24 754 | - Lint 17d03e2 755 | - Fixes - Type for simplicity - Lean with hydration of dates and ids - Distinct hydration of dates and ids - Apply args for callbacks - Some tests to cover above c363e2b 756 | - Update changelog for "v1.0.2" e04dc70 757 | 758 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.2...v1.0.3 759 | 760 | [Changes][v1.0.3] 761 | 762 | 763 | 764 | # [v1.0.2](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.2) - 2023-05-02 765 | 766 | - Release 36e67ed 767 | - Merge pull request [#9](https://github.com/ilovepixelart/ts-cache-mongoose/issues/9) from ilovepixelart/feature/test 4b850da 768 | - Fix rehydration scenario for lean objects 25e2caf 769 | - Test 820d958 770 | - Merge pull request [#8](https://github.com/ilovepixelart/ts-cache-mongoose/issues/8) from ilovepixelart/feature/dep b0fcbef 771 | - Dep 66e8265 772 | - Doc 49a26ad 773 | - Update README.md 61c4494 774 | - Update changelog for "v1.0.1" cc5c31e 775 | 776 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.1...v1.0.2 777 | 778 | [Changes][v1.0.2] 779 | 780 | 781 | 782 | # [v1.0.1](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.1) - 2023-04-30 783 | 784 | - Dep e67c1e3 785 | - Dep cc8ebd2 786 | - Merge pull request [#5](https://github.com/ilovepixelart/ts-cache-mongoose/issues/5) from ilovepixelart/feature/remove-dependency 39cba89 787 | - Dep 9a3e0bb 788 | - Remove dependency 811df42 789 | - Cache npm 0298794 790 | - Key e9b19a5 791 | - Update changelog for "v1.0.0" 777e8c1 792 | 793 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.0...v1.0.1 794 | 795 | [Changes][v1.0.1] 796 | 797 | 798 | 799 | # [v1.0.0](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v1.0.0) - 2023-04-29 800 | 801 | - Doc dcaa81f 802 | - Cleanup bd95d5e 803 | - Description 2f18d42 804 | - Description 4115926 805 | - Doc 684cbbd 806 | - Merge pull request [#4](https://github.com/ilovepixelart/ts-cache-mongoose/issues/4) from ilovepixelart/feature/test 33a5d0c 807 | - Test 962f8b1 808 | - Doc 0b7286a 809 | - Merge pull request [#3](https://github.com/ilovepixelart/ts-cache-mongoose/issues/3) from ilovepixelart/feature/dep 165321e 810 | - Dep bbd94bf 811 | - Update changelog for "v0.0.6" c184433 812 | 813 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.6...v1.0.0 814 | 815 | [Changes][v1.0.0] 816 | 817 | 818 | 819 | # [v0.0.6](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v0.0.6) - 2023-04-28 820 | 821 | - Key b27f663 822 | - Hydration cases aec2d0b 823 | - Update changelog for "v0.0.5" 92756a9 824 | 825 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.5...v0.0.6 826 | 827 | [Changes][v0.0.6] 828 | 829 | 830 | 831 | # [v0.0.5](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v0.0.5) - 2023-04-27 832 | 833 | - Doc 11f0282 834 | - Doc 6dc537d 835 | 836 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.4...v0.0.5 837 | 838 | [Changes][v0.0.5] 839 | 840 | 841 | 842 | # [v0.0.4](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v0.0.4) - 2023-04-27 843 | 844 | - Merge pull request [#2](https://github.com/ilovepixelart/ts-cache-mongoose/issues/2) from ilovepixelart/feature/aggregate 40ed36b 845 | - Test 5af6586 846 | - Refactor hashing bc291de 847 | - Cleanup 6f48f36 848 | - Test 2df4b5e 849 | - Aggregate 7d87eb7 850 | 851 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.3...v0.0.4 852 | 853 | [Changes][v0.0.4] 854 | 855 | 856 | 857 | # [v0.0.3](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v0.0.3) - 2023-04-27 858 | 859 | - Doc 796d8ae 860 | 861 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.2...v0.0.3 862 | 863 | [Changes][v0.0.3] 864 | 865 | 866 | 867 | # [v0.0.2](https://github.com/ilovepixelart/ts-cache-mongoose/releases/tag/v0.0.2) - 2023-04-27 868 | 869 | - Doc 3a1a96e 870 | - Features 546f96d 871 | - Merge pull request [#1](https://github.com/ilovepixelart/ts-cache-mongoose/issues/1) from ilovepixelart/feature/cache-engine c1e2adc 872 | - Redis localhost 73e000f 873 | - Redis pipeline 6897e9d 874 | - Redis 001b158 875 | - More tests 90f01f4 876 | - Redis test 39fb0f5 877 | - Cache bug fix ttl, more tests 8761c96 878 | - Doc c745179 879 | - Cache engine - Close underlying connection - Regis cache options support - Delete old in memory cache - Test for redis be18959 880 | - Name 3c9b736 881 | - Dep dfca630 882 | - Dep 63ee84f 883 | 884 | https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.1...v0.0.2 885 | 886 | [Changes][v0.0.2] 887 | 888 | 889 | [v1.7.3]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.7.2...v1.7.3 890 | [v1.7.2]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.7.1...v1.7.2 891 | [v1.7.1]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.7.0...v1.7.1 892 | [v1.7.0]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.8...v1.7.0 893 | [v1.6.8]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.7...v1.6.8 894 | [v1.6.7]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.6...v1.6.7 895 | [v1.6.6]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.5...v1.6.6 896 | [v1.6.5]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.4...v1.6.5 897 | [v1.6.4]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.3...v1.6.4 898 | [v1.6.3]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.2...v1.6.3 899 | [v1.6.2]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.1...v1.6.2 900 | [v1.6.1]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.6.0...v1.6.1 901 | [v1.6.0]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.5.0...v1.6.0 902 | [v1.5.0]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.7...v1.5.0 903 | [v1.4.7]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.6...v1.4.7 904 | [v1.4.6]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.5...v1.4.6 905 | [v1.4.5]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.4...v1.4.5 906 | [v1.4.4]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.3...v1.4.4 907 | [v1.4.3]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.2...v1.4.3 908 | [v1.4.2]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.1...v1.4.2 909 | [v1.4.1]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.4.0...v1.4.1 910 | [v1.4.0]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.3.0...v1.4.0 911 | [v1.3.0]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.2.1...v1.3.0 912 | [v1.2.1]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.2.0...v1.2.1 913 | [v1.2.0]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.1.2...v1.2.0 914 | [v1.1.2]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.1.1...v1.1.2 915 | [v1.1.1]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.1.0...v1.1.1 916 | [v1.1.0]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.9...v1.1.0 917 | [v1.0.9]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.8...v1.0.9 918 | [v1.0.8]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.7...v1.0.8 919 | [v1.0.7]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.6...v1.0.7 920 | [v1.0.6]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.5...v1.0.6 921 | [v1.0.5]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.4...v1.0.5 922 | [v1.0.4]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.3...v1.0.4 923 | [v1.0.3]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.2...v1.0.3 924 | [v1.0.2]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.1...v1.0.2 925 | [v1.0.1]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v1.0.0...v1.0.1 926 | [v1.0.0]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.6...v1.0.0 927 | [v0.0.6]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.5...v0.0.6 928 | [v0.0.5]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.4...v0.0.5 929 | [v0.0.4]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.3...v0.0.4 930 | [v0.0.3]: https://github.com/ilovepixelart/ts-cache-mongoose/compare/v0.0.2...v0.0.3 931 | [v0.0.2]: https://github.com/ilovepixelart/ts-cache-mongoose/tree/v0.0.2 932 | 933 | 934 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 4 | 5 | ## Our Standards 6 | 7 | Examples of behavior that contributes to creating a positive environment include: 8 | 9 | * Using welcoming and inclusive language 10 | * Being respectful of differing viewpoints and experiences 11 | * Gracefully accepting constructive criticism 12 | * Focusing on what is best for the community 13 | * Showing empathy towards other community members 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 18 | * Trolling, insulting/derogatory comments, and personal or political attacks 19 | * Public or private harassment 20 | * Publishing others’ private information, such as a physical or electronic address, without explicit permission 21 | * Other conduct which could reasonably be considered inappropriate in a professional setting 22 | 23 | ## Our Responsibilities 24 | 25 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 26 | 27 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 28 | 29 | ## Scope 30 | 31 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 32 | 33 | ## Enforcement 34 | 35 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. 36 | 37 | ## Attribution 38 | 39 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at 40 | 41 | For answers to common questions about this code of conduct, see 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | 1. Start an issue. We will discuss the best approach 4 | 2. Make a pull request. I'll review it and comment until we are both confident about it 5 | 3. I'll merge your PR and bump the version of the package 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 ilovepixelart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts-cache-mongoose 2 | 3 | Cache query and aggregate in mongoose using in-memory or redis 4 | 5 | [![npm](https://img.shields.io/npm/v/ts-cache-mongoose)](https://www.npmjs.com/package/ts-cache-mongoose) 6 | [![npm](https://img.shields.io/npm/dt/ts-cache-mongoose)](https://www.npmjs.com/package/ts-cache-mongoose) 7 | [![GitHub](https://img.shields.io/github/license/ilovepixelart/ts-cache-mongoose)](https://github.com/ilovepixelart/ts-cache-mongoose/blob/main/LICENSE) 8 | \ 9 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ilovepixelart_ts-cache-mongoose&metric=coverage)](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-cache-mongoose) 10 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ilovepixelart_ts-cache-mongoose&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-cache-mongoose) 11 | \ 12 | [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=ilovepixelart_ts-cache-mongoose&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-cache-mongoose) 13 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ilovepixelart_ts-cache-mongoose&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-cache-mongoose) 14 | [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=ilovepixelart_ts-cache-mongoose&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-cache-mongoose) 15 | 16 | ## Motivation 17 | 18 | ts-cache-mongoose is a plugin for mongoose 19 | \ 20 | Caching queries is a good way to improve performance of your application 21 | 22 | ## Supports and tested with 23 | 24 | ```json 25 | { 26 | "node": "18.x || 20.x || 22.x", 27 | "mongoose": ">=6.6.x || 7.x || 8.x", 28 | } 29 | ``` 30 | 31 | ## Features 32 | 33 | - In-memory caching 34 | - Redis caching 35 | - Cache expiration 36 | - Cache invalidation 37 | - Cache key generation 38 | - Cache key prefix 39 | - Query caching 40 | - Aggregate caching 41 | - Supports ESM and CommonJS 42 | 43 | ## Installation 44 | 45 | - Locally inside your project 46 | 47 | ```bash 48 | npm install ts-cache-mongoose 49 | pnpm add ts-cache-mongoose 50 | yarn add ts-cache-mongoose 51 | bun add ts-cache-mongoose 52 | ``` 53 | 54 | - This plugin requires mongoose `>=6.6.x || 7.x || 8.x` to be installed as a peer dependency 55 | 56 | ```bash 57 | # For latest mongoose 6 58 | npm install mongoose@6 59 | pnpm add mongoose@6 60 | yarn add mongoose@6 61 | bun add mongoose@6 62 | # For latest mongoose 7 63 | npm install mongoose@7 64 | pnpm add mongoose@7 65 | yarn add mongoose@7 66 | bun add mongoose@7 67 | # For latest mongoose 8 68 | npm install mongoose@8 69 | pnpm add mongoose@8 70 | yarn add mongoose@8 71 | bun add mongoose@8 72 | ``` 73 | 74 | ## Example 75 | 76 | ```typescript 77 | // On your application startup 78 | import mongoose from 'mongoose' 79 | import cache from 'ts-cache-mongoose' 80 | 81 | // In-memory example 82 | const instance = cache.init(mongoose, { 83 | defaultTTL: '60 seconds', 84 | engine: 'memory', 85 | debug: true, // Debug mode enables hit/miss logs in console, not for production use 86 | }) 87 | 88 | // OR 89 | 90 | // Redis example 91 | const instance = cache.init(mongoose, { 92 | defaultTTL: '60 seconds', 93 | engine: 'redis', 94 | engineOptions: { 95 | host: 'localhost', 96 | port: 6379, 97 | }, 98 | debug: true, // Debug mode enables hit/miss logs in console, not for production use 99 | }) 100 | 101 | // Connect to your database 102 | mongoose.connect('mongodb://localhost:27017/my-database') 103 | 104 | // Somewhere in your code 105 | const users = await User.find({ role: 'user' }).cache('10 seconds').exec() 106 | // Cache hit 107 | const users = await User.find({ role: 'user' }).cache('10 seconds').exec() 108 | 109 | const book = await Book.findById(id).cache('1 hour').exec() 110 | const bookCount = await Book.countDocuments().cache('1 minute').exec() 111 | const authors = await Book.distinct('author').cache('30 seconds').exec() 112 | 113 | const books = await Book.aggregate([ 114 | { 115 | $match: { 116 | genre: 'fantasy', 117 | }, 118 | }, 119 | { 120 | $group: { 121 | _id: '$author', 122 | count: { $sum: 1 }, 123 | }, 124 | }, 125 | { 126 | $project: { 127 | _id: 0, 128 | author: '$_id', 129 | count: 1, 130 | }, 131 | } 132 | ]).cache('1 minute').exec() 133 | 134 | // Cache invalidation 135 | 136 | // To clear all cache, don't use in production unless you know what you are doing 137 | await instance.clear() 138 | 139 | // Instead use custom cache key 140 | const user = await User.findById('61bb4d6a1786e5123d7f4cf1').cache('1 minute', 'some-custom-key').exec() 141 | await instance.clear('some-custom-key') 142 | ``` 143 | 144 | ## Check my other projects 145 | 146 | - [ts-migrate-mongoose](https://github.com/ilovepixelart/ts-migrate-mongoose) - Migration framework for mongoose 147 | - [ts-patch-mongoose](https://github.com/ilovepixelart/ts-patch-mongoose) - Patch history & events plugin for mongoose 148 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, 4 | "files": { 5 | "ignoreUnknown": false, 6 | "ignore": ["node_modules", "dist", "coverage"] 7 | }, 8 | "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 }, 9 | "organizeImports": { "enabled": true }, 10 | "linter": { 11 | "enabled": true, 12 | "rules": { 13 | "recommended": true 14 | } 15 | }, 16 | "javascript": { 17 | "formatter": { 18 | "trailingCommas": "all", 19 | "quoteStyle": "single", 20 | "semicolons": "asNeeded", 21 | "lineWidth": 320 22 | }, 23 | "globals": ["Atomics", "SharedArrayBuffer"] 24 | }, 25 | "json": { 26 | "formatter": { 27 | "trailingCommas": "none" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-cache-mongoose", 3 | "version": "1.7.3", 4 | "description": "Cache plugin for mongoose Queries and Aggregate (in-memory, redis)", 5 | "author": "ilovepixelart", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/ilovepixelart/ts-cache-mongoose.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/ilovepixelart/ts-cache-mongoose/issues" 13 | }, 14 | "homepage": "https://github.com/ilovepixelart/ts-cache-mongoose#readme", 15 | "directories": { 16 | "examples": "examples" 17 | }, 18 | "keywords": [ 19 | "backend", 20 | "mongo", 21 | "mongodb", 22 | "mongoose", 23 | "plugin", 24 | "schema", 25 | "db", 26 | "nosql", 27 | "ts", 28 | "typescript", 29 | "cache", 30 | "redis", 31 | "store", 32 | "memory", 33 | "ttl", 34 | "query", 35 | "aggregate" 36 | ], 37 | "engines": { 38 | "node": ">=16" 39 | }, 40 | "files": [ 41 | "dist", 42 | "src", 43 | "tests", 44 | "tsconfig.json", 45 | "vite.config.mts", 46 | "biome.json" 47 | ], 48 | "type": "module", 49 | "exports": { 50 | "require": { 51 | "types": "./dist/index.d.cts", 52 | "default": "./dist/index.cjs" 53 | }, 54 | "import": { 55 | "types": "./dist/index.d.mts", 56 | "default": "./dist/index.mjs" 57 | } 58 | }, 59 | "main": "./dist/index.cjs", 60 | "module": "./dist/index.mjs", 61 | "types": "./dist/index.d.cts", 62 | "scripts": { 63 | "prepare": "simple-git-hooks", 64 | "biome": "npx @biomejs/biome check", 65 | "biome:fix": "npx @biomejs/biome check --write .", 66 | "test": "vitest run --coverage", 67 | "test:open": "vitest run --coverage && open-cli coverage/lcov-report/index.html", 68 | "clean": "rm -rf ./dist", 69 | "type:check": "tsc --noEmit", 70 | "build": "pkgroll --clean-dist", 71 | "release": "npm install && npm run biome && npm run type:check && npm run build && np" 72 | }, 73 | "dependencies": { 74 | "@types/ms": "2.1.0", 75 | "@types/semver": "7.7.0", 76 | "ioredis": "5.6.1", 77 | "ms": "2.1.3", 78 | "semver": "7.7.2", 79 | "sort-keys": "4.2.0" 80 | }, 81 | "devDependencies": { 82 | "@biomejs/biome": "1.9.4", 83 | "@types/node": "22.15.30", 84 | "@vitest/coverage-v8": "3.2.2", 85 | "bson": "^6.10.3", 86 | "mongodb-memory-server": "10.1.4", 87 | "mongoose": "8.15.1", 88 | "open-cli": "8.0.0", 89 | "pkgroll": "2.12.2", 90 | "simple-git-hooks": "2.13.0", 91 | "typescript": "5.8.3", 92 | "vitest": "3.2.2" 93 | }, 94 | "peerDependencies": { 95 | "bson": ">=4.7.2 < 7", 96 | "mongoose": ">=6.6.0 < 9" 97 | }, 98 | "simple-git-hooks": { 99 | "pre-commit": "npm run type:check", 100 | "pre-push": "npm run biome:fix" 101 | }, 102 | "overrides": { 103 | "esbuild": "0.25.0" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/cache/Cache.ts: -------------------------------------------------------------------------------- 1 | import ms from 'ms' 2 | 3 | import { MemoryCacheEngine } from './engine/MemoryCacheEngine' 4 | import { RedisCacheEngine } from './engine/RedisCacheEngine' 5 | 6 | import type { CacheData, CacheEngine, CacheOptions, CacheTTL } from '../types' 7 | 8 | export class Cache { 9 | readonly #engine!: CacheEngine 10 | readonly #defaultTTL: number 11 | readonly #debug: boolean 12 | readonly #engines = ['memory', 'redis'] as const 13 | 14 | constructor(cacheOptions: CacheOptions) { 15 | if (!this.#engines.includes(cacheOptions.engine)) { 16 | throw new Error(`Invalid engine name: ${cacheOptions.engine}`) 17 | } 18 | 19 | if (cacheOptions.engine === 'redis' && !cacheOptions.engineOptions) { 20 | throw new Error(`Engine options are required for ${cacheOptions.engine} engine`) 21 | } 22 | 23 | if (!cacheOptions.defaultTTL) { 24 | cacheOptions.defaultTTL = '1 minute' 25 | } 26 | 27 | this.#defaultTTL = typeof cacheOptions.defaultTTL === 'string' ? ms(cacheOptions.defaultTTL) : cacheOptions.defaultTTL 28 | 29 | if (cacheOptions.engine === 'redis' && cacheOptions.engineOptions) { 30 | this.#engine = new RedisCacheEngine(cacheOptions.engineOptions) 31 | } 32 | 33 | if (cacheOptions.engine === 'memory') { 34 | this.#engine = new MemoryCacheEngine() 35 | } 36 | 37 | this.#debug = cacheOptions.debug === true 38 | } 39 | 40 | async get(key: string): Promise { 41 | const cacheEntry = await this.#engine.get(key) 42 | if (this.#debug) { 43 | const cacheHit = cacheEntry != null ? 'HIT' : 'MISS' 44 | console.log(`[ts-cache-mongoose] GET '${key}' - ${cacheHit}`) 45 | } 46 | return cacheEntry 47 | } 48 | 49 | async set(key: string, value: CacheData, ttl: CacheTTL | null): Promise { 50 | const givenTTL = typeof ttl === 'string' ? ms(ttl) : ttl 51 | const actualTTL = givenTTL ?? this.#defaultTTL 52 | await this.#engine.set(key, value, actualTTL) 53 | if (this.#debug) { 54 | console.log(`[ts-cache-mongoose] SET '${key}' - ttl: ${actualTTL.toFixed(0)} ms`) 55 | } 56 | } 57 | 58 | async del(key: string): Promise { 59 | await this.#engine.del(key) 60 | if (this.#debug) { 61 | console.log(`[ts-cache-mongoose] DEL '${key}'`) 62 | } 63 | } 64 | 65 | async clear(): Promise { 66 | await this.#engine.clear() 67 | if (this.#debug) { 68 | console.log('[ts-cache-mongoose] CLEAR') 69 | } 70 | } 71 | 72 | async close(): Promise { 73 | return this.#engine.close() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/cache/engine/MemoryCacheEngine.ts: -------------------------------------------------------------------------------- 1 | import ms from 'ms' 2 | 3 | import type { CacheData, CacheEngine, CacheTTL } from '../../types' 4 | 5 | export class MemoryCacheEngine implements CacheEngine { 6 | readonly #cache: Map 7 | 8 | constructor() { 9 | this.#cache = new Map() 10 | } 11 | 12 | get(key: string): CacheData { 13 | const item = this.#cache.get(key) 14 | if (!item || item.expiresAt < Date.now()) { 15 | this.del(key) 16 | return undefined 17 | } 18 | return item.value 19 | } 20 | 21 | set(key: string, value: CacheData, ttl?: CacheTTL): void { 22 | const givenTTL = typeof ttl === 'string' ? ms(ttl) : ttl 23 | const actualTTL = givenTTL ?? Number.POSITIVE_INFINITY 24 | this.#cache.set(key, { 25 | value, 26 | expiresAt: Date.now() + actualTTL, 27 | }) 28 | } 29 | 30 | del(key: string): void { 31 | this.#cache.delete(key) 32 | } 33 | 34 | clear(): void { 35 | this.#cache.clear() 36 | } 37 | 38 | close(): void { 39 | // do nothing 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/cache/engine/RedisCacheEngine.ts: -------------------------------------------------------------------------------- 1 | import { EJSON } from 'bson' 2 | import IORedis from 'ioredis' 3 | import ms from 'ms' 4 | 5 | import { convertToObject } from '../../version' 6 | 7 | import type { Redis, RedisOptions } from 'ioredis' 8 | import type { CacheData, CacheEngine, CacheTTL } from '../../types' 9 | 10 | export class RedisCacheEngine implements CacheEngine { 11 | readonly #client: Redis 12 | 13 | constructor(options: RedisOptions) { 14 | if (!options.keyPrefix) { 15 | options.keyPrefix = 'cache-mongoose:' 16 | } 17 | this.#client = new IORedis(options) 18 | } 19 | 20 | async get(key: string): Promise { 21 | try { 22 | const value = await this.#client.get(key) 23 | if (value === null) { 24 | return undefined 25 | } 26 | return EJSON.parse(value) as CacheData 27 | } catch (err) { 28 | console.error(err) 29 | return undefined 30 | } 31 | } 32 | 33 | async set(key: string, value: CacheData, ttl?: CacheTTL): Promise { 34 | try { 35 | const givenTTL = typeof ttl === 'string' ? ms(ttl) : ttl 36 | const actualTTL = givenTTL ?? Number.POSITIVE_INFINITY 37 | const serializedValue = EJSON.stringify(convertToObject(value)) 38 | await this.#client.setex(key, Math.ceil(actualTTL / 1000), serializedValue) 39 | } catch (err) { 40 | console.error(err) 41 | } 42 | } 43 | 44 | async del(key: string): Promise { 45 | await this.#client.del(key) 46 | } 47 | 48 | async clear(): Promise { 49 | await this.#client.flushdb() 50 | } 51 | 52 | async close(): Promise { 53 | await this.#client.quit() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/extend/aggregate.ts: -------------------------------------------------------------------------------- 1 | import { getKey } from '../key' 2 | 3 | import type { Mongoose } from 'mongoose' 4 | import type { Cache } from '../cache/Cache' 5 | import type { CacheTTL } from '../types' 6 | 7 | export function extendAggregate(mongoose: Mongoose, cache: Cache): void { 8 | const mongooseExec = mongoose.Aggregate.prototype.exec 9 | 10 | mongoose.Aggregate.prototype.getCacheKey = function (): string { 11 | if (this._key != null) return this._key 12 | 13 | return getKey({ 14 | pipeline: this.pipeline(), 15 | }) 16 | } 17 | 18 | mongoose.Aggregate.prototype.getCacheTTL = function (): CacheTTL | null { 19 | return this._ttl 20 | } 21 | 22 | mongoose.Aggregate.prototype.cache = function (ttl?: CacheTTL, customKey?: string) { 23 | this._ttl = ttl ?? null 24 | this._key = customKey ?? null 25 | return this 26 | } 27 | 28 | mongoose.Aggregate.prototype.exec = async function (...args: []) { 29 | if (!Object.prototype.hasOwnProperty.call(this, '_ttl')) { 30 | return mongooseExec.apply(this, args) 31 | } 32 | 33 | const key = this.getCacheKey() 34 | const ttl = this.getCacheTTL() 35 | 36 | const resultCache = await cache.get(key).catch((err: unknown) => { 37 | console.error(err) 38 | }) 39 | 40 | if (resultCache) { 41 | return resultCache 42 | } 43 | 44 | const result = (await mongooseExec.call(this)) as Record[] | Record 45 | await cache.set(key, result, ttl).catch((err: unknown) => { 46 | console.error(err) 47 | }) 48 | 49 | return result 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/extend/query.ts: -------------------------------------------------------------------------------- 1 | import { getKey } from '../key' 2 | 3 | import type { Mongoose } from 'mongoose' 4 | import type { Cache } from '../cache/Cache' 5 | import type { CacheTTL } from '../types' 6 | 7 | export function extendQuery(mongoose: Mongoose, cache: Cache): void { 8 | const mongooseExec = mongoose.Query.prototype.exec 9 | 10 | mongoose.Query.prototype.getCacheKey = function (): string { 11 | if (this._key != null) return this._key 12 | 13 | const filter = this.getFilter() 14 | const update = this.getUpdate() 15 | const options = this.getOptions() 16 | const mongooseOptions = this.mongooseOptions() 17 | 18 | return getKey({ 19 | model: this.model.modelName, 20 | op: this.op, 21 | filter, 22 | update, 23 | options, 24 | mongooseOptions, 25 | _path: this._path, 26 | _fields: this._fields, 27 | _distinct: this._distinct, 28 | _conditions: this._conditions, 29 | }) 30 | } 31 | 32 | mongoose.Query.prototype.getCacheTTL = function (): CacheTTL | null { 33 | return this._ttl 34 | } 35 | 36 | mongoose.Query.prototype.cache = function (ttl?: CacheTTL, customKey?: string) { 37 | this._ttl = ttl ?? null 38 | this._key = customKey ?? null 39 | return this 40 | } 41 | 42 | mongoose.Query.prototype.exec = async function (...args: []) { 43 | if (!Object.prototype.hasOwnProperty.call(this, '_ttl')) { 44 | return mongooseExec.apply(this, args) 45 | } 46 | 47 | const key = this.getCacheKey() 48 | const ttl = this.getCacheTTL() 49 | const mongooseOptions = this.mongooseOptions() 50 | 51 | const isCount = this.op?.includes('count') ?? false 52 | const isDistinct = this.op === 'distinct' 53 | const model = this.model.modelName 54 | 55 | const resultCache = await cache.get(key).catch((err: unknown) => { 56 | console.error(err) 57 | }) 58 | 59 | if (resultCache) { 60 | if (isCount || isDistinct || mongooseOptions.lean) { 61 | return resultCache 62 | } 63 | 64 | const modelConstructor = mongoose.model(model) 65 | 66 | if (Array.isArray(resultCache)) { 67 | return resultCache.map((item) => { 68 | return modelConstructor.hydrate(item) 69 | }) 70 | } 71 | return modelConstructor.hydrate(resultCache) 72 | } 73 | 74 | const result = (await mongooseExec.call(this)) as Record[] | Record 75 | await cache.set(key, result, ttl).catch((err: unknown) => { 76 | console.error(err) 77 | }) 78 | 79 | return result 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Cache } from './cache/Cache' 2 | import { extendAggregate } from './extend/aggregate' 3 | import { extendQuery } from './extend/query' 4 | 5 | import type { Mongoose } from 'mongoose' 6 | import type { CacheOptions, CacheTTL } from './types' 7 | 8 | export * from './types' 9 | 10 | declare module 'mongoose' { 11 | interface Query { 12 | cache: (this: Query, ttl?: CacheTTL, customKey?: string) => this 13 | _key: string | null 14 | getCacheKey: (this: Query) => string 15 | _ttl: CacheTTL | null 16 | getCacheTTL: (this: Query) => CacheTTL | null 17 | op?: string 18 | _path?: unknown 19 | _fields?: unknown 20 | _distinct?: unknown 21 | _conditions?: unknown 22 | } 23 | 24 | interface Aggregate { 25 | cache: (this: Aggregate, ttl?: CacheTTL, customKey?: string) => this 26 | _key: string | null 27 | getCacheKey: (this: Aggregate) => string 28 | _ttl: CacheTTL | null 29 | getCacheTTL: (this: Aggregate) => CacheTTL | null 30 | } 31 | } 32 | 33 | class CacheMongoose { 34 | static #instance: CacheMongoose | undefined 35 | private cache!: Cache 36 | 37 | private constructor() { 38 | // Private constructor to prevent external instantiation 39 | } 40 | 41 | public static init(mongoose: Mongoose, cacheOptions: CacheOptions): CacheMongoose { 42 | if (!CacheMongoose.#instance) { 43 | CacheMongoose.#instance = new CacheMongoose() 44 | CacheMongoose.#instance.cache = new Cache(cacheOptions) 45 | 46 | const cache = CacheMongoose.#instance.cache 47 | 48 | extendQuery(mongoose, cache) 49 | extendAggregate(mongoose, cache) 50 | } 51 | 52 | return CacheMongoose.#instance 53 | } 54 | 55 | public async clear(customKey?: string): Promise { 56 | if (customKey != null) { 57 | await this.cache.del(customKey) 58 | } else { 59 | await this.cache.clear() 60 | } 61 | } 62 | 63 | public async close(): Promise { 64 | await this.cache.close() 65 | } 66 | } 67 | 68 | export default CacheMongoose 69 | -------------------------------------------------------------------------------- /src/key.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from 'node:crypto' 2 | import sortKeys from 'sort-keys' 3 | 4 | export function getKey(data: Record[] | Record): string { 5 | const sortedObj = sortKeys(data, { deep: true }) 6 | const sortedStr = JSON.stringify(sortedObj, (_, val: unknown) => { 7 | return val instanceof RegExp ? String(val) : val 8 | }) 9 | return createHash('sha1').update(sortedStr).digest('hex') 10 | } 11 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { RedisOptions } from 'ioredis' 2 | import type { StringValue } from 'ms' 3 | 4 | export type CacheTTL = number | StringValue 5 | 6 | export type CacheData = Record | Record[] | unknown[] | number | undefined 7 | 8 | export type CacheOptions = { 9 | engine: 'memory' | 'redis' 10 | engineOptions?: RedisOptions 11 | defaultTTL?: CacheTTL 12 | debug?: boolean 13 | } 14 | 15 | export interface CacheEngine { 16 | get: (key: string) => Promise | CacheData 17 | set: (key: string, value: CacheData, ttl?: CacheTTL) => Promise | void 18 | del: (key: string) => Promise | void 19 | clear: () => Promise | void 20 | close: () => Promise | void 21 | } 22 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import { satisfies } from 'semver' 3 | 4 | import type { CacheData } from './types' 5 | 6 | export const isMongooseLessThan7 = satisfies(mongoose.version, '<7') 7 | 8 | export const convertToObject = (value: (T & { toObject?: () => CacheData }) | undefined): CacheData => { 9 | if (isMongooseLessThan7) { 10 | if (value != null && typeof value === 'object' && !Array.isArray(value) && value.toObject) { 11 | return value.toObject() 12 | } 13 | if (Array.isArray(value)) { 14 | return value.map((doc) => convertToObject(doc)) 15 | } 16 | } 17 | 18 | return value 19 | } 20 | -------------------------------------------------------------------------------- /tests/cache-debug.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' 2 | 3 | import mongoose from 'mongoose' 4 | import CacheMongoose from '../src/index' 5 | import { UserModel } from './models/User' 6 | import { server } from './mongo/server' 7 | 8 | describe('cache-debug', async () => { 9 | const instance = server('cache-debug') 10 | let cache: CacheMongoose 11 | 12 | beforeAll(async () => { 13 | cache = CacheMongoose.init(mongoose, { 14 | engine: 'memory', 15 | debug: true, 16 | }) 17 | 18 | await instance.create() 19 | }) 20 | 21 | afterAll(async () => { 22 | await cache.clear() 23 | await cache.close() 24 | await instance.destroy() 25 | }) 26 | 27 | beforeEach(async () => { 28 | vi.spyOn(global.console, 'log') 29 | await mongoose.connection.collection('users').deleteMany({}) 30 | }) 31 | 32 | afterEach(async () => { 33 | vi.restoreAllMocks() 34 | }) 35 | 36 | describe('debug scenarios', () => { 37 | it('should create a use and and query it two time first is cache miss second is hit, also clear by key and global', async () => { 38 | const user = await UserModel.create({ 39 | name: 'John Doe', 40 | role: 'admin', 41 | }) 42 | 43 | const key = 'key' 44 | const ttl = '1 second' 45 | const cacheMissRegExp = /\[ts-cache-mongoose\] GET '.*?' - MISS/ 46 | const cacheHitRegExp = /\[ts-cache-mongoose\] GET '.*?' - HIT/ 47 | const cacheSetRegExp = /\[ts-cache-mongoose\] SET '.*?' - ttl: \d+ ms/ 48 | const cacheClearRegExp = /\[ts-cache-mongoose\] CLEAR/ 49 | const cacheDelRegExp = /\[ts-cache-mongoose\] DEL '.*?'/ 50 | 51 | const userCacheMiss = await UserModel.findById(user._id).cache(ttl, key).exec() 52 | expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheMissRegExp)) 53 | expect(userCacheMiss).not.toBeNull() 54 | expect(userCacheMiss?._id.toString()).toBe(user._id.toString()) 55 | expect(userCacheMiss?.name).toEqual(user.name) 56 | expect(userCacheMiss?.role).toEqual(user.role) 57 | 58 | const userCacheHit = await UserModel.findById(user._id).cache(ttl, key).exec() 59 | expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheSetRegExp)) 60 | expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheHitRegExp)) 61 | expect(userCacheHit).not.toBeNull() 62 | expect(userCacheHit?._id.toString()).toBe(user._id.toString()) 63 | expect(userCacheHit?.name).toEqual(user.name) 64 | expect(userCacheHit?.role).toEqual(user.role) 65 | 66 | await cache.clear(key) 67 | expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheDelRegExp)) 68 | 69 | await cache.clear() 70 | expect(console.log).toHaveBeenCalledWith(expect.stringMatching(cacheClearRegExp)) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /tests/cache-memory.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' 2 | 3 | import mongoose from 'mongoose' 4 | import CacheMongoose from '../src/index' 5 | import { UserModel } from './models/User' 6 | import { server } from './mongo/server' 7 | 8 | describe('cache-memory', async () => { 9 | const instance = server('cache-memory') 10 | let cache: CacheMongoose 11 | 12 | beforeAll(async () => { 13 | cache = CacheMongoose.init(mongoose, { 14 | engine: 'memory', 15 | }) 16 | 17 | await instance.create() 18 | }) 19 | 20 | afterAll(async () => { 21 | await cache.clear() 22 | await cache.close() 23 | await instance.destroy() 24 | }) 25 | 26 | beforeEach(async () => { 27 | await mongoose.connection.collection('users').deleteMany({}) 28 | }) 29 | 30 | describe('memory scenarios', () => { 31 | it('should use memory cache', async () => { 32 | const user = await UserModel.create({ 33 | name: 'John Doe', 34 | role: 'admin', 35 | }) 36 | 37 | const user1 = await UserModel.findById(user._id).cache().exec() 38 | await UserModel.findOneAndUpdate({ _id: user._id }, { name: 'John Doe 2' }).exec() 39 | const user2 = await UserModel.findById(user._id).cache().exec() 40 | 41 | expect(user1).not.toBeNull() 42 | expect(user2).not.toBeNull() 43 | expect(user1?._id.toString()).toBe(user2?._id.toString()) 44 | expect(user1?.name).toEqual(user2?.name) 45 | }) 46 | 47 | it('should not use cache', async () => { 48 | const user = await UserModel.create({ 49 | name: 'John Doe', 50 | role: 'admin', 51 | }) 52 | 53 | const cache1 = await UserModel.findById(user._id).cache().exec() 54 | await UserModel.findByIdAndUpdate(user._id, { name: 'John Doe 2' }).exec() 55 | await cache.clear() 56 | const cache2 = await UserModel.findById(user._id).cache().exec() 57 | 58 | expect(cache1).not.toBeNull() 59 | expect(cache2).not.toBeNull() 60 | expect(cache1?._id.toString()).toBe(cache2?._id.toString()) 61 | expect(cache1?.name).not.toEqual(cache2?.name) 62 | }) 63 | 64 | it('should use memory cache with custom key', async () => { 65 | const user = await UserModel.create({ 66 | name: 'John Doe', 67 | role: 'admin', 68 | }) 69 | 70 | const cache1 = await UserModel.findById(user._id).cache('1 minute', 'test-custom-key').exec() 71 | await UserModel.findOneAndUpdate({ _id: user._id }, { name: 'John Doe 2' }).exec() 72 | const cache2 = await UserModel.findById(user._id).cache('1 minute', 'test-custom-key').exec() 73 | 74 | expect(cache1).not.toBeNull() 75 | expect(cache2).not.toBeNull() 76 | expect(cache1?._id.toString()).toBe(cache2?._id.toString()) 77 | expect(cache1?.name).toEqual(cache2?.name) 78 | }) 79 | 80 | it('should use memory cache and clear custom key', async () => { 81 | const user = await UserModel.create({ 82 | name: 'John Doe', 83 | role: 'admin', 84 | }) 85 | 86 | const cache1 = await UserModel.findById(user._id).cache('1 minute', 'test-custom-key-second').exec() 87 | await UserModel.updateOne({ _id: user._id }, { name: 'John Doe 2' }).exec() 88 | await cache.clear('test-custom-key-second') 89 | const cache2 = await UserModel.findById(user._id).cache('1 minute', 'test-custom-key-second').exec() 90 | 91 | expect(cache1).not.toBeNull() 92 | expect(cache2).not.toBeNull() 93 | expect(cache1?._id.toString()).toBe(cache2?._id.toString()) 94 | expect(cache1?.name).not.toEqual(cache2?.name) 95 | }) 96 | 97 | it('should use memory cache and custom key with an empty string', async () => { 98 | const user = await UserModel.create({ 99 | name: 'John Doe', 100 | role: 'admin', 101 | }) 102 | 103 | const cache1 = await UserModel.findById(user._id).cache('1 minute', '').exec() 104 | await UserModel.updateOne({ _id: user._id }, { name: 'John Doe 2' }).exec() 105 | const cache2 = await UserModel.findById(user._id).cache('1 minute', '').exec() 106 | 107 | expect(cache1).not.toBeNull() 108 | expect(cache2).not.toBeNull() 109 | expect(cache1?._id.toString()).toBe(cache2?._id.toString()) 110 | expect(cache1?.name).toEqual(cache2?.name) 111 | 112 | await cache.clear('') 113 | const cache3 = await UserModel.findById(user._id).cache('1 minute', '').exec() 114 | expect(cache3).not.toBeNull() 115 | expect(cache2?._id.toString()).toBe(cache3?._id.toString()) 116 | expect(cache2?.name).not.toEqual(cache3?.name) 117 | }) 118 | 119 | it('should use memory cache and aggregate', async () => { 120 | await UserModel.create([ 121 | { name: 'John', role: 'admin' }, 122 | { name: 'Bob', role: 'admin' }, 123 | { name: 'Alice', role: 'user' }, 124 | ]) 125 | 126 | const cache1 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }]) 127 | .cache() 128 | .exec() 129 | 130 | await UserModel.create({ name: 'Mark', role: 'admin' }) 131 | 132 | const cache2 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }]) 133 | .cache() 134 | .exec() 135 | 136 | expect(cache1).not.toBeNull() 137 | expect(cache2).not.toBeNull() 138 | expect(cache1?.[0].count).toEqual(cache2?.[0].count) 139 | }) 140 | }) 141 | 142 | describe('memory scenarios with ttl', () => { 143 | const users = [ 144 | { name: 'John', age: 30, role: 'admin' }, 145 | { name: 'Alice', age: 25, role: 'user' }, 146 | { name: 'Bob', age: 35, role: 'user' }, 147 | ] 148 | 149 | beforeEach(async () => { 150 | // Delete all users before each test 151 | await UserModel.deleteMany().exec() 152 | 153 | // Create new users 154 | await UserModel.create(users) 155 | }) 156 | 157 | it('findById', async () => { 158 | const john = await UserModel.create({ name: 'John', age: 30, role: 'admin' }) 159 | const user = await UserModel.findById(john._id).cache('1 minute').exec() 160 | const cachedUser = await UserModel.findById(john._id).cache('1 minute').exec() 161 | 162 | expect(user?._id.toString()).toBe(cachedUser?._id.toString()) 163 | expect(user?.name).toEqual(cachedUser?.name) 164 | expect(user?.createdAt).toEqual(cachedUser?.createdAt) 165 | expect(user?.updatedAt).toEqual(cachedUser?.updatedAt) 166 | }) 167 | 168 | it('findOne', async () => { 169 | const user = await UserModel.findOne({ name: 'John', age: 30, role: 'admin' }).cache('1 minute').exec() 170 | await UserModel.create({ name: 'Steve', age: 30, role: 'admin' }) 171 | const cachedUser = await UserModel.findOne({ 172 | name: 'John', 173 | age: 30, 174 | role: 'admin', 175 | }) 176 | .cache('1 minute') 177 | .exec() 178 | 179 | expect(user?._id.toString()).toBe(cachedUser?._id.toString()) 180 | expect(user?.name).toEqual(cachedUser?.name) 181 | expect(user?.createdAt).toEqual(cachedUser?.createdAt) 182 | expect(user?.updatedAt).toEqual(cachedUser?.updatedAt) 183 | }) 184 | 185 | it('find', async () => { 186 | const users = await UserModel.find({ age: { $gte: 30 } }) 187 | .cache('1 minute') 188 | .exec() 189 | await UserModel.create({ name: 'Steve', age: 30, role: 'admin' }) 190 | const cachedUsers = await UserModel.find({ age: { $gte: 30 } }) 191 | .cache('1 minute') 192 | .exec() 193 | 194 | expect(users).toHaveLength(cachedUsers.length) 195 | }) 196 | 197 | it('count', async () => { 198 | const count = await UserModel.countDocuments({ age: { $gte: 30 } }) 199 | .cache('1 minute') 200 | .exec() 201 | await UserModel.create({ name: 'Steve', age: 30, role: 'admin' }) 202 | const cachedCount = await UserModel.countDocuments({ age: { $gte: 30 } }) 203 | .cache('1 minute') 204 | .exec() 205 | 206 | expect(count).toEqual(cachedCount) 207 | }) 208 | 209 | it('distinct', async () => { 210 | const emails = await UserModel.distinct('name').cache('1 minute').exec() 211 | await UserModel.create({ name: 'Steve', age: 30, role: 'admin' }) 212 | const cachedEmails = await UserModel.distinct('name').cache('1 minute').exec() 213 | 214 | expect(emails).toEqual(cachedEmails) 215 | }) 216 | }) 217 | }) 218 | -------------------------------------------------------------------------------- /tests/cache-options.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | import { Cache } from '../src/cache/Cache' 3 | 4 | import type { CacheOptions } from '../src/types' 5 | 6 | describe('Cache class tests', () => { 7 | it('should create a new instance of Cache', () => { 8 | const cacheOptions: CacheOptions = { 9 | engine: 'memory', 10 | } 11 | 12 | const cache = new Cache(cacheOptions) 13 | expect(cache).toBeInstanceOf(Cache) 14 | expect(cache).toHaveProperty('get') 15 | expect(cache).toHaveProperty('set') 16 | expect(cache).toHaveProperty('del') 17 | expect(cache).toHaveProperty('clear') 18 | expect(cache).toHaveProperty('close') 19 | }) 20 | 21 | it('should throw an error if the cache engine is not supported', () => { 22 | const cacheOptions: CacheOptions = { 23 | // @ts-expect-error Testing invalid engine name 24 | engine: 'not-supported', 25 | } 26 | 27 | expect(() => new Cache(cacheOptions)).toThrow(`Invalid engine name: ${cacheOptions.engine}`) 28 | }) 29 | 30 | it('should throw an error if the cache engine is redis and no engine options are provided', () => { 31 | const cacheOptions: CacheOptions = { 32 | engine: 'redis', 33 | } 34 | 35 | expect(() => new Cache(cacheOptions)).toThrow(`Engine options are required for ${cacheOptions.engine} engine`) 36 | }) 37 | 38 | it('should create a new instance of Cache with redis engine', async () => { 39 | const cacheOptions: CacheOptions = { 40 | engine: 'redis', 41 | engineOptions: { 42 | host: 'localhost', 43 | port: 6379, 44 | }, 45 | defaultTTL: '10 minutes', 46 | } 47 | 48 | const cache = new Cache(cacheOptions) 49 | expect(cache).toBeInstanceOf(Cache) 50 | expect(cache).toHaveProperty('get') 51 | expect(cache).toHaveProperty('set') 52 | expect(cache).toHaveProperty('del') 53 | expect(cache).toHaveProperty('clear') 54 | expect(cache).toHaveProperty('close') 55 | 56 | await cache.set('bob', { test: 'bob' }, null) 57 | await cache.set('john', { test: 'john' }, '1 minute') 58 | 59 | const value = await cache.get('bob') 60 | expect(value).toEqual({ test: 'bob' }) 61 | 62 | await cache.del('bob') 63 | const clearBob = await cache.get('bob') 64 | expect(clearBob).toBeUndefined() 65 | 66 | const john = await cache.get('john') 67 | expect(john).toEqual({ test: 'john' }) 68 | 69 | await cache.clear() 70 | const clearJohn = await cache.get('john') 71 | expect(clearJohn).toBeUndefined() 72 | 73 | const mockSet = vi.fn() 74 | cache.set = mockSet 75 | 76 | await cache.set('bob', { test: 'bob' }, null) 77 | expect(mockSet).toHaveBeenCalled() 78 | expect(mockSet).toHaveBeenCalledWith('bob', { test: 'bob' }, null) 79 | 80 | await cache.close() 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /tests/cache-redis.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' 2 | 3 | import mongoose from 'mongoose' 4 | import plugin from '../src/index' 5 | import { server } from './mongo/server' 6 | 7 | import { ObjectId } from 'bson' 8 | import { StoryModel } from './models/Story' 9 | import { UserModel } from './models/User' 10 | 11 | import type CacheMongoose from '../src/index' 12 | 13 | describe('cache-redis', async () => { 14 | const instance = server('cache-redis') 15 | let cache: CacheMongoose 16 | 17 | beforeAll(async () => { 18 | cache = plugin.init(mongoose, { 19 | engine: 'redis', 20 | engineOptions: { 21 | host: 'localhost', 22 | port: 6379, 23 | }, 24 | defaultTTL: '10 seconds', 25 | }) 26 | 27 | await instance.create() 28 | }) 29 | 30 | afterAll(async () => { 31 | await cache.clear() 32 | await cache.close() 33 | await instance.destroy() 34 | }) 35 | 36 | beforeEach(async () => { 37 | await mongoose.connection.collection('users').deleteMany({}) 38 | await mongoose.connection.collection('stories').deleteMany({}) 39 | }) 40 | 41 | it('should use cache', async () => { 42 | const user = await UserModel.create({ 43 | name: 'John Doe', 44 | role: 'admin', 45 | }) 46 | 47 | const user1 = await UserModel.findById(user._id).cache().exec() 48 | await UserModel.findOneAndUpdate({ _id: user._id }, { name: 'John Doe 2' }).exec() 49 | const user2 = await UserModel.findById(user._id).cache().exec() 50 | 51 | expect(user1).not.toBeNull() 52 | expect(user2).not.toBeNull() 53 | expect(user1?._id.toString()).toBe(user2?._id.toString()) 54 | expect(user1?.name).toEqual(user2?.name) 55 | }) 56 | 57 | it('should clear cache', async () => { 58 | const user = await UserModel.create({ 59 | name: 'John Doe', 60 | role: 'admin', 61 | }) 62 | 63 | const cache1 = await UserModel.findById(user._id).cache().exec() 64 | await UserModel.findByIdAndUpdate(user._id, { name: 'Steve' }).exec() 65 | await cache.clear() 66 | const cache2 = await UserModel.findById(user._id).cache().exec() 67 | 68 | expect(cache1).not.toBeNull() 69 | expect(cache2).not.toBeNull() 70 | expect(cache1?._id.toString()).toBe(cache2?._id.toString()) 71 | expect(cache1?.name).not.toEqual(cache2?.name) 72 | }) 73 | 74 | it('should use cache with custom-key-1', async () => { 75 | const user = await UserModel.create({ 76 | name: 'John Doe', 77 | role: 'admin', 78 | }) 79 | 80 | const cache1 = await UserModel.findById(user._id).cache('30 seconds', 'custom-key-1').exec() 81 | await UserModel.findOneAndUpdate({ _id: user._id }, { name: 'John Doe 2' }).exec() 82 | const cache2 = await UserModel.findById(user._id).cache('30 seconds', 'custom-key-1').exec() 83 | 84 | expect(cache1).not.toBeNull() 85 | expect(cache2).not.toBeNull() 86 | expect(cache1?._id.toString()).toBe(cache2?._id.toString()) 87 | expect(cache1?.name).toEqual(cache2?.name) 88 | }) 89 | 90 | it('should clear custom-key-2', async () => { 91 | const user = await UserModel.create({ 92 | name: 'John Doe', 93 | role: 'admin', 94 | }) 95 | 96 | const cache1 = await UserModel.findById(user._id).cache('30 seconds', 'custom-key-2').exec() 97 | await UserModel.updateOne({ _id: user._id }, { name: 'Steve' }).exec() 98 | await cache.clear('custom-key-2') 99 | const cache2 = await UserModel.findById(user._id).cache('30 seconds', 'custom-key-2').exec() 100 | 101 | expect(cache1).not.toBeNull() 102 | expect(cache2).not.toBeNull() 103 | expect(cache1?._id.toString()).toBe(cache2?._id.toString()) 104 | expect(cache1?.name).not.toEqual(cache2?.name) 105 | }) 106 | 107 | it('should use cache without cache options', async () => { 108 | expect(cache).toBeDefined() 109 | 110 | const user = await UserModel.create({ 111 | name: 'John Doe', 112 | role: 'admin', 113 | }) 114 | 115 | const cache1 = await UserModel.findById(user._id).cache().exec() 116 | await UserModel.updateOne({ _id: user._id }, { name: 'John Doe 2' }).exec() 117 | const cache2 = await UserModel.findById(user._id).cache().exec() 118 | 119 | expect(cache1).not.toBeNull() 120 | expect(cache2).not.toBeNull() 121 | expect(cache1?._id.toString()).toBe(cache2?._id.toString()) 122 | expect(cache1?.name).toEqual(cache2?.name) 123 | 124 | await UserModel.create([ 125 | { 126 | name: 'Alice', 127 | role: 'admin', 128 | }, 129 | { 130 | name: 'Bob', 131 | role: 'admin', 132 | }, 133 | ]) 134 | 135 | const cache3 = await UserModel.find({ role: 'admin' }).cache().exec() 136 | await UserModel.updateMany({ role: 'admin' }, { name: 'Steve' }).exec() 137 | const cache4 = await UserModel.find({ role: 'admin' }).cache().exec() 138 | 139 | expect(cache3).not.toBeNull() 140 | expect(cache4).not.toBeNull() 141 | expect(cache3?.length).toEqual(cache4?.length) 142 | expect(cache3?.[0].name).toEqual(cache4?.[0].name) 143 | }) 144 | 145 | it('should use cache on aggregate', async () => { 146 | await UserModel.create([ 147 | { name: 'John', role: 'admin' }, 148 | { name: 'Bob', role: 'admin' }, 149 | { name: 'Alice', role: 'user' }, 150 | ]) 151 | 152 | const cache1 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }]) 153 | .cache('30 seconds') 154 | .exec() 155 | 156 | await UserModel.create({ name: 'Mark', role: 'admin' }) 157 | 158 | const cache2 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }]) 159 | .cache('30 seconds') 160 | .exec() 161 | 162 | expect(cache1).not.toBeNull() 163 | expect(cache2).not.toBeNull() 164 | expect(cache1?.[0].count).toEqual(cache2?.[0].count) 165 | }) 166 | 167 | it('should use cache on aggregate with custom-key', async () => { 168 | await UserModel.create([ 169 | { name: 'John', role: 'admin' }, 170 | { name: 'Bob', role: 'admin' }, 171 | { name: 'Alice', role: 'user' }, 172 | ]) 173 | 174 | const cache1 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }]) 175 | .cache('30 seconds', 'aggregate-custom-key') 176 | .exec() 177 | 178 | await UserModel.create({ name: 'Mark', role: 'admin' }) 179 | 180 | const cache2 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }]) 181 | .cache('30 seconds', 'aggregate-custom-key') 182 | .exec() 183 | 184 | // Don't use cache key 185 | const cache3 = await UserModel.aggregate([{ $match: { role: 'admin' } }, { $group: { _id: '$role', count: { $sum: 1 } } }]).exec() 186 | 187 | expect(cache1).not.toBeNull() 188 | expect(cache2).not.toBeNull() 189 | expect(cache3).not.toBeNull() 190 | expect(cache1?.[0].count).toEqual(cache2?.[0].count) 191 | expect(cache1?.[0].count).not.toEqual(cache3?.[0].count) 192 | }) 193 | 194 | it('should test lean cache', async () => { 195 | await UserModel.create([ 196 | { name: 'John', role: 'admin' }, 197 | { name: 'Bob', role: 'admin' }, 198 | { name: 'Alice', role: 'user' }, 199 | ]) 200 | 201 | const cache1 = await UserModel.find({ role: 'admin' }).lean().cache('30 seconds').exec() 202 | await UserModel.create({ name: 'Mark', role: 'admin' }) 203 | const cache2 = await UserModel.find({ role: 'admin' }).lean().cache('30 seconds').exec() 204 | 205 | expect(cache1).not.toBeNull() 206 | expect(cache2).not.toBeNull() 207 | expect(cache1?.length).toEqual(cache2?.length) 208 | }) 209 | 210 | it('should cache with regex query', async () => { 211 | await UserModel.create([ 212 | { name: 'John', role: 'admin' }, 213 | { name: 'Alice', role: 'user' }, 214 | { name: 'Andy', role: 'user' }, 215 | ]) 216 | 217 | const cache1 = await UserModel.find({ name: /^J/ }).cache('30 seconds').exec() 218 | await UserModel.create({ name: 'Jenifer', role: 'admin' }) 219 | const cache2 = await UserModel.find({ name: /^J/ }).cache('30 seconds').exec() 220 | 221 | expect(cache1).not.toBeNull() 222 | expect(cache2).not.toBeNull() 223 | expect(cache1?.length).toEqual(cache2?.length) 224 | 225 | const cache3 = await UserModel.find({ name: /^A/ }).cache('30 seconds').exec() 226 | await UserModel.create({ name: 'Alex', role: 'admin' }) 227 | const cache4 = await UserModel.find({ name: /^A/ }).cache('30 seconds').exec() 228 | 229 | expect(cache3).not.toBeNull() 230 | expect(cache4).not.toBeNull() 231 | expect(cache3?.length).toEqual(cache4?.length) 232 | }) 233 | 234 | it('should findOne', async () => { 235 | await UserModel.create([ 236 | { name: 'C', role: 'admin' }, 237 | { name: 'V', role: 'user' }, 238 | { name: 'G', role: 'user' }, 239 | ]) 240 | 241 | const miss = await UserModel.findOne({ name: 'G' }).lean().cache('30 seconds').exec() 242 | expect(miss).not.toBeNull() 243 | 244 | expect(typeof miss?._id).toBe('object') 245 | expect(miss?._id instanceof mongoose.Types.ObjectId).toBeTruthy() 246 | 247 | expect(miss).toHaveProperty('name', 'G') 248 | 249 | const hit = await UserModel.findOne({ name: 'G' }).lean().cache('30 seconds').exec() 250 | expect(hit).not.toBeNull() 251 | 252 | expect(typeof hit?._id).toBe('object') 253 | expect(hit?._id instanceof ObjectId).toBeTruthy() 254 | 255 | expect(hit).toHaveProperty('name', 'G') 256 | 257 | expect(miss?._id.toString()).toBe(hit?._id.toString()) 258 | expect(miss?.name).toEqual(hit?.name) 259 | expect(miss?.role).toEqual(hit?.role) 260 | expect(miss?.createdAt).toEqual(hit?.createdAt) 261 | expect(miss?.updatedAt).toEqual(hit?.updatedAt) 262 | }) 263 | 264 | it('should distinct("_id") and distinct("role") and distinct("createdAt")', async () => { 265 | await UserModel.create({ name: 'i', role: 'admin' }) 266 | await UserModel.create({ name: 'p', role: 'user' }) 267 | await UserModel.create({ name: 'm', role: 'user' }) 268 | 269 | const miss = await UserModel.distinct('_id').cache('30 seconds').exec() 270 | expect(miss).not.toBeNull() 271 | expect(miss?.length).toBe(3) 272 | 273 | expect(typeof miss?.[0]).toBe('object') 274 | expect(miss?.[0] instanceof mongoose.Types.ObjectId).toBeTruthy() 275 | 276 | const hit = await UserModel.distinct('_id').cache('30 seconds').exec() 277 | expect(hit).not.toBeNull() 278 | expect(hit?.length).toBe(3) 279 | 280 | expect(typeof hit?.[0]).toBe('object') 281 | expect(hit?.[0] instanceof ObjectId).toBeTruthy() 282 | 283 | const cache4 = await UserModel.distinct('role').cache('30 seconds').exec() 284 | expect(cache4).not.toBeNull() 285 | expect(cache4?.length).toBe(2) 286 | expect(cache4).toEqual(['admin', 'user']) 287 | 288 | const cache5 = await UserModel.distinct('role').cache('30 seconds').exec() 289 | expect(cache5).not.toBeNull() 290 | expect(cache5?.length).toBe(2) 291 | expect(cache5).toEqual(['admin', 'user']) 292 | 293 | const cache6 = await UserModel.distinct('name').cache('30 seconds').exec() 294 | expect(cache6).not.toBeNull() 295 | expect(cache6?.length).toBe(3) 296 | 297 | const cache7 = await UserModel.distinct('name').cache('30 seconds').exec() 298 | 299 | expect(miss.map((id) => id.toString())).toEqual(hit.map((id) => id.toString())) 300 | expect(cache4).toEqual(cache5) 301 | expect(cache6).toEqual(cache7) 302 | }) 303 | 304 | it('should test exceptions', async () => { 305 | const user = await UserModel.create({ name: 'i', role: 'admin' }) 306 | const story1 = await StoryModel.create({ title: '1', userId: user._id }) 307 | const story2 = await StoryModel.create({ title: '2', userId: user._id }) 308 | 309 | const miss = await UserModel.findOne({ name: 'i' }).populate({ path: 'stories' }).lean().cache('30 seconds').exec() 310 | const hit = await UserModel.findOne({ name: 'i' }).populate({ path: 'stories' }).lean().cache('30 seconds').exec() 311 | 312 | expect(miss).not.toBeNull() 313 | 314 | expect(typeof miss?._id).toBe('object') 315 | expect(miss?._id instanceof mongoose.Types.ObjectId).toBeTruthy() 316 | 317 | expect(miss?.name).toBe('i') 318 | expect(miss?.stories).not.toBeNull() 319 | expect(miss?.stories?.length).toBe(2) 320 | 321 | expect(miss?.stories?.[0]._id.toString()).toBe(story1._id.toString()) 322 | 323 | expect(typeof miss?.stories?.[0]._id).toBe('object') 324 | expect(miss?.stories?.[0]._id instanceof mongoose.Types.ObjectId).toBeTruthy() 325 | 326 | expect(typeof miss?.stories?.[0].createdAt).toBe('object') 327 | expect(miss?.stories?.[0].createdAt instanceof Date).toBeTruthy() 328 | 329 | expect(miss?.stories?.[1]._id.toString()).toBe(story2._id.toString()) 330 | 331 | expect(typeof miss?.stories?.[1]._id).toBe('object') 332 | expect(miss?.stories?.[1]._id instanceof mongoose.Types.ObjectId).toBeTruthy() 333 | 334 | expect(typeof miss?.stories?.[1].createdAt).toBe('object') 335 | expect(miss?.stories?.[1].createdAt instanceof Date).toBeTruthy() 336 | 337 | expect(hit).not.toBeNull() 338 | 339 | expect(typeof hit?._id).toBe('object') 340 | expect(hit?._id instanceof ObjectId).toBeTruthy() 341 | 342 | expect(hit?.name).toBe('i') 343 | expect(hit?.stories).not.toBeNull() 344 | expect(hit?.stories?.length).toBe(2) 345 | 346 | expect(hit?.stories?.[0]._id.toString()).toBe(story1._id.toString()) 347 | 348 | expect(typeof hit?.stories?.[0]._id).toBe('object') 349 | expect(hit?.stories?.[0]._id instanceof ObjectId).toBeTruthy() 350 | 351 | expect(hit?.stories?.[0].createdAt instanceof Date).toBeTruthy() 352 | expect(typeof hit?.stories?.[0].createdAt).toBe('object') 353 | 354 | expect(hit?.stories?.[1]._id.toString()).toBe(story2._id.toString()) 355 | 356 | expect(typeof hit?.stories?.[1]._id).toBe('object') 357 | expect(hit?.stories?.[1]._id instanceof ObjectId).toBeTruthy() 358 | 359 | expect(hit?.stories?.[1].createdAt instanceof Date).toBeTruthy() 360 | expect(typeof hit?.stories?.[1].createdAt).toBe('object') 361 | 362 | expect(miss?._id.toString()).toBe(hit?._id.toString()) 363 | expect(miss?.name).toBe(hit?.name) 364 | expect(miss?.role).toBe(hit?.role) 365 | expect(miss?.createdAt?.toString()).toBe(hit?.createdAt?.toString()) 366 | expect(miss?.updatedAt?.toString()).toBe(hit?.updatedAt?.toString()) 367 | 368 | expect(miss?.stories?.[0]._id.toString()).toBe(hit?.stories?.[0]._id.toString()) 369 | expect(miss?.stories?.[0].title).toBe(hit?.stories?.[0].title) 370 | expect(miss?.stories?.[0].userId.toString()).toBe(hit?.stories?.[0].userId.toString()) 371 | expect(miss?.stories?.[0].createdAt?.toString()).toBe(hit?.stories?.[0].createdAt?.toString()) 372 | expect(miss?.stories?.[0].updatedAt?.toString()).toBe(hit?.stories?.[0].updatedAt?.toString()) 373 | 374 | expect(miss?.stories?.[1]._id.toString()).toBe(hit?.stories?.[1]._id.toString()) 375 | expect(miss?.stories?.[1].title).toBe(hit?.stories?.[1].title) 376 | expect(miss?.stories?.[1].userId.toString()).toBe(hit?.stories?.[1].userId.toString()) 377 | expect(miss?.stories?.[1].createdAt?.toString()).toBe(hit?.stories?.[1].createdAt?.toString()) 378 | expect(miss?.stories?.[1].updatedAt?.toString()).toBe(hit?.stories?.[1].updatedAt?.toString()) 379 | }) 380 | 381 | it('should not misclassify certain fields as objectIds', async () => { 382 | // ObjectId.isValid will return true for multiple scenarios. 383 | // A string being a potentially valid objectId should not be the 384 | // determining factor on wether or not deserialize it as objectId. 385 | const user = await UserModel.create({ 386 | name: '12CharString', 387 | role: '660ef695677786928202dc1f', 388 | }) 389 | const pureLean = await UserModel.findOne({ _id: user._id }).lean() 390 | 391 | const miss = await UserModel.findOne({ _id: user._id }).lean().cache('30 seconds') 392 | const hit = await UserModel.findOne({ _id: user._id }).lean().cache('30 seconds') 393 | 394 | expect(pureLean).not.toBeNull() 395 | expect(typeof pureLean?._id).toBe('object') 396 | expect(typeof pureLean?.createdAt).toBe('object') 397 | 398 | expect(miss).not.toBeNull() 399 | expect(typeof miss?._id).toBe('object') 400 | expect(typeof miss?.createdAt).toBe('object') 401 | 402 | expect(hit).not.toBeNull() 403 | expect(typeof hit?._id).toBe('object') 404 | expect(typeof hit?.createdAt).toBe('object') 405 | 406 | expect(miss?._id.toString()).toBe(hit?._id.toString()) 407 | expect(miss?.role).toEqual(hit?.role) 408 | expect(miss?.createdAt).toEqual(hit?.createdAt) 409 | 410 | const distinctMiss = await UserModel.distinct('_id').cache('30 seconds').lean().exec() 411 | expect(distinctMiss).not.toBeNull() 412 | expect(distinctMiss?.length).toBe(1) 413 | expect(distinctMiss).toEqual([pureLean?._id]) 414 | 415 | const distinctHit = await UserModel.distinct('_id').cache('30 seconds').lean().exec() 416 | expect(distinctHit).not.toBeNull() 417 | expect(distinctHit?.length).toBe(1) 418 | expect(distinctHit.map((id) => id.toString())).toEqual([pureLean?._id.toString()]) 419 | 420 | const distinctCreatedAtMiss = await UserModel.distinct('createdAt').cache('30 seconds').lean().exec() 421 | expect(distinctCreatedAtMiss).not.toBeNull() 422 | expect(distinctCreatedAtMiss?.length).toBe(1) 423 | expect(typeof distinctCreatedAtMiss?.[0]).toBe('object') 424 | expect(distinctCreatedAtMiss?.[0] instanceof Date).toBeTruthy() 425 | expect(distinctCreatedAtMiss).toEqual([pureLean?.createdAt]) 426 | 427 | const distinctCreatedAtHit = await UserModel.distinct('createdAt').cache('30 seconds').lean().exec() 428 | expect(distinctCreatedAtHit).not.toBeNull() 429 | expect(distinctCreatedAtHit?.length).toBe(1) 430 | expect(typeof distinctCreatedAtMiss?.[0]).toBe('object') 431 | expect(distinctCreatedAtMiss?.[0] instanceof Date).toBeTruthy() 432 | expect(distinctCreatedAtHit).toEqual([pureLean?.createdAt]) 433 | 434 | expect(miss?._id.toString()).toBe(hit?._id.toString()) 435 | expect(miss?.name).toBe(hit?.name) 436 | expect(miss?.role).toBe(hit?.role) 437 | expect(miss?.createdAt?.toString()).toBe(hit?.createdAt?.toString()) 438 | expect(miss?.updatedAt?.toString()).toBe(hit?.updatedAt?.toString()) 439 | }) 440 | 441 | it('should hydrate populated objects from cache', async () => { 442 | const user = await UserModel.create({ name: 'Alex', role: 'user' }) 443 | const story1 = await StoryModel.create({ title: 'Ticket 1', userId: user._id }) 444 | const story2 = await StoryModel.create({ title: 'Ticket 2', userId: user._id }) 445 | 446 | const populatedOriginal = await UserModel.findOne({ name: 'Alex' }).populate('stories').lean().cache('1 minute').exec() 447 | 448 | expect(populatedOriginal).not.toBeNull() 449 | 450 | expect(typeof populatedOriginal?._id).toBe('object') 451 | expect(populatedOriginal?._id instanceof mongoose.Types.ObjectId).toBeTruthy() 452 | 453 | expect(populatedOriginal?.name).toBe('Alex') 454 | expect(populatedOriginal?.stories).not.toBeNull() 455 | expect(populatedOriginal?.stories?.length).toBe(2) 456 | 457 | expect(populatedOriginal?.stories?.[0]._id.toString()).toBe(story1._id.toString()) 458 | 459 | expect(typeof populatedOriginal?.stories?.[0]._id).toBe('object') 460 | expect(populatedOriginal?.stories?.[0]._id instanceof mongoose.Types.ObjectId).toBeTruthy() 461 | 462 | expect(typeof populatedOriginal?.stories?.[0].createdAt).toBe('object') 463 | expect(populatedOriginal?.stories?.[0].createdAt instanceof Date).toBeTruthy() 464 | 465 | expect(populatedOriginal?.stories?.[1]._id.toString()).toBe(story2._id.toString()) 466 | 467 | expect(typeof populatedOriginal?.stories?.[1]._id).toBe('object') 468 | expect(populatedOriginal?.stories?.[1]._id instanceof mongoose.Types.ObjectId).toBeTruthy() 469 | 470 | expect(typeof populatedOriginal?.stories?.[1].createdAt).toBe('object') 471 | expect(populatedOriginal?.stories?.[1].createdAt instanceof Date).toBeTruthy() 472 | 473 | // Deleting user and stories, to ensure that the cache is used 474 | await UserModel.deleteOne({ _id: user._id }).exec() 475 | await StoryModel.deleteMany({ userId: user._id }).exec() 476 | 477 | const populatedCache = await UserModel.findOne({ name: 'Alex' }).populate('stories').lean().cache('1 minute').exec() 478 | 479 | expect(populatedCache).not.toBeNull() 480 | 481 | expect(typeof populatedCache?._id).toBe('object') 482 | expect(populatedCache?._id instanceof ObjectId).toBeTruthy() 483 | 484 | expect(populatedCache?.name).toBe('Alex') 485 | expect(populatedCache?.stories).not.toBeNull() 486 | expect(populatedCache?.stories?.length).toBe(2) 487 | 488 | expect(populatedCache?.stories?.[0]._id.toString()).toBe(story1._id.toString()) 489 | 490 | expect(typeof populatedCache?.stories?.[0]._id).toBe('object') 491 | expect(populatedCache?.stories?.[0]._id instanceof ObjectId).toBeTruthy() 492 | 493 | expect(typeof populatedCache?.stories?.[0].createdAt).toBe('object') 494 | expect(populatedCache?.stories?.[0].createdAt instanceof Date).toBeTruthy() 495 | 496 | expect(populatedCache?.stories?.[1]._id.toString()).toBe(story2._id.toString()) 497 | 498 | expect(typeof populatedCache?.stories?.[1]._id).toBe('object') 499 | expect(populatedCache?.stories?.[1]._id instanceof ObjectId).toBeTruthy() 500 | 501 | expect(typeof populatedCache?.stories?.[1].createdAt).toBe('object') 502 | expect(populatedCache?.stories?.[1].createdAt instanceof Date).toBeTruthy() 503 | 504 | expect(populatedOriginal?._id.toString()).toBe(populatedCache?._id.toString()) 505 | expect(populatedOriginal?.name).toBe(populatedCache?.name) 506 | expect(populatedOriginal?.role).toBe(populatedCache?.role) 507 | expect(populatedOriginal?.createdAt?.toString()).toBe(populatedCache?.createdAt?.toString()) 508 | expect(populatedOriginal?.updatedAt?.toString()).toBe(populatedCache?.updatedAt?.toString()) 509 | 510 | expect(populatedOriginal?.stories?.[0]._id.toString()).toBe(populatedCache?.stories?.[0]._id.toString()) 511 | expect(populatedOriginal?.stories?.[0].title).toBe(populatedCache?.stories?.[0].title) 512 | expect(populatedOriginal?.stories?.[0].userId.toString()).toBe(populatedCache?.stories?.[0].userId.toString()) 513 | expect(populatedOriginal?.stories?.[0].createdAt?.toString()).toBe(populatedCache?.stories?.[0].createdAt?.toString()) 514 | expect(populatedOriginal?.stories?.[0].updatedAt?.toString()).toBe(populatedCache?.stories?.[0].updatedAt?.toString()) 515 | 516 | expect(populatedOriginal?.stories?.[1]._id.toString()).toBe(populatedCache?.stories?.[1]._id.toString()) 517 | expect(populatedOriginal?.stories?.[1].title).toBe(populatedCache?.stories?.[1].title) 518 | expect(populatedOriginal?.stories?.[1].userId.toString()).toBe(populatedCache?.stories?.[1].userId.toString()) 519 | expect(populatedOriginal?.stories?.[1].createdAt?.toString()).toBe(populatedCache?.stories?.[1].createdAt?.toString()) 520 | expect(populatedOriginal?.stories?.[1].updatedAt?.toString()).toBe(populatedCache?.stories?.[1].updatedAt?.toString()) 521 | }) 522 | }) 523 | -------------------------------------------------------------------------------- /tests/key.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { Types } from 'mongoose' 4 | import { getKey } from '../src/key' 5 | 6 | const { ObjectId } = Types 7 | 8 | describe('generateHash()', () => { 9 | const data1 = { 10 | foo: 42, 11 | bar: { 12 | baz: [3, 2, 1], 13 | qux: 'hello', 14 | _id: new ObjectId('5f9b3b3b3b3b3b3b3b3b3b3b'), 15 | wow: { 16 | word: 'world', 17 | hey: { 18 | waldo: true, 19 | fred: null, 20 | missing: undefined, 21 | }, 22 | }, 23 | }, 24 | } 25 | 26 | const data2 = { 27 | foo: 42, 28 | bar: { 29 | _id: new ObjectId('5f9b3b3b3b3b3b3b3b3b3b3b'), 30 | baz: [3, 2, 1], 31 | qux: 'hello', 32 | wow: { 33 | word: 'world', 34 | hey: { 35 | waldo: true, 36 | fred: null, 37 | missing: undefined, 38 | }, 39 | }, 40 | }, 41 | } 42 | 43 | const data3 = { 44 | _id: new ObjectId('5f9b3b3b3b3b3b3b3b3b3b3b'), 45 | bar: { 46 | qux: 'hello', 47 | baz: [3, 2, 1], 48 | wow: { 49 | hey: { 50 | fred: null, 51 | waldo: true, 52 | missing: undefined, 53 | }, 54 | word: 'world', 55 | }, 56 | }, 57 | foo: 42, 58 | } 59 | 60 | const data4 = { 61 | _id: new ObjectId('1f9b3b3b3b3b3b3b3b3b3b3b'), 62 | bar: { 63 | qux: 'hello', 64 | baz: [3, 2, 1], 65 | wow: { 66 | hey: { 67 | fred: null, 68 | waldo: true, 69 | missing: undefined, 70 | }, 71 | word: 'world', 72 | }, 73 | }, 74 | foo: 42, 75 | } 76 | 77 | it('should generate hash keys for objects with different key orders', () => { 78 | const hash1 = getKey(data1) 79 | const hash2 = getKey(data2) 80 | const hash3 = getKey(data3) 81 | const hash4 = getKey(data4) 82 | 83 | expect(hash1).toEqual(hash2) 84 | expect(hash1).not.toEqual(hash3) 85 | expect(hash3).not.toEqual(hash4) 86 | }) 87 | 88 | it('should test dates', async () => { 89 | const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) 90 | const date = new Date() 91 | const hash1 = getKey({ 92 | name: 'John Doe', 93 | date: { $lte: date }, 94 | }) 95 | await wait(50) 96 | const date2 = new Date() 97 | const hash2 = getKey({ 98 | name: 'John Doe', 99 | date: { $lte: date2 }, 100 | }) 101 | expect(hash1).not.toEqual(hash2) 102 | }) 103 | }) 104 | -------------------------------------------------------------------------------- /tests/models/Story.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model, models } from 'mongoose' 2 | 3 | import type { Model, Types } from 'mongoose' 4 | 5 | export interface Story { 6 | _id: Types.ObjectId 7 | userId: Types.ObjectId 8 | title: string 9 | createdAt: Date 10 | updatedAt: Date 11 | } 12 | 13 | export const StorySchema = new Schema( 14 | { 15 | userId: { 16 | type: Schema.Types.ObjectId, 17 | ref: 'User', 18 | required: true, 19 | index: true, 20 | }, 21 | title: { 22 | type: String, 23 | required: true, 24 | }, 25 | }, 26 | { timestamps: true }, 27 | ) 28 | 29 | export const StoryModel = (models.Story as Model | undefined) ?? model('Story', StorySchema) 30 | -------------------------------------------------------------------------------- /tests/models/User.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model, models } from 'mongoose' 2 | 3 | import type { HydratedDocument, Model, Types } from 'mongoose' 4 | import type { Story } from './Story' 5 | 6 | export interface User { 7 | _id: Types.ObjectId 8 | name: string 9 | role: string 10 | age?: number 11 | createdAt?: Date 12 | updatedAt?: Date 13 | stories?: HydratedDocument[] 14 | } 15 | 16 | export const UserSchema = new Schema( 17 | { 18 | name: { 19 | type: String, 20 | required: true, 21 | }, 22 | role: { 23 | type: String, 24 | required: true, 25 | }, 26 | age: { 27 | type: Number, 28 | }, 29 | }, 30 | { timestamps: true }, 31 | ) 32 | 33 | UserSchema.virtual('stories', { 34 | ref: 'Story', 35 | localField: '_id', 36 | foreignField: 'userId', 37 | }) 38 | 39 | export const UserModel = (models.User as Model | undefined) ?? model('User', UserSchema) 40 | -------------------------------------------------------------------------------- /tests/mongo/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !server.ts -------------------------------------------------------------------------------- /tests/mongo/server.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import mongoose from 'mongoose' 3 | 4 | import { MongoMemoryServer } from 'mongodb-memory-server' 5 | 6 | export const server = (dbName: string) => { 7 | let mongo: MongoMemoryServer 8 | const dbPath = `./tests/mongo/${dbName}` 9 | 10 | const create = async () => { 11 | fs.mkdirSync(dbPath, { recursive: true }) 12 | mongo = await MongoMemoryServer.create({ 13 | instance: { 14 | dbName, 15 | dbPath, 16 | }, 17 | }) 18 | 19 | const uri = mongo.getUri() 20 | await mongoose.connect(uri) 21 | } 22 | 23 | const destroy = async () => { 24 | await mongoose.connection.dropDatabase() 25 | await mongoose.connection.close() 26 | await mongo.stop({ doCleanup: true, force: true }) 27 | } 28 | 29 | return { create, destroy } 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "target": "ES2021", 5 | "lib": ["ES2021"], 6 | "types": ["node"], 7 | "module": "Preserve", 8 | "moduleResolution": "bundler", 9 | "outDir": "dist", 10 | "strict": true, 11 | "allowSyntheticDefaultImports": true, 12 | "allowUnreachableCode": false, 13 | "allowUnusedLabels": false, 14 | "checkJs": true, 15 | "declaration": true, 16 | "declarationMap": true, 17 | "esModuleInterop": true, 18 | "exactOptionalPropertyTypes": true, 19 | "forceConsistentCasingInFileNames": true, 20 | "importHelpers": true, 21 | "isolatedModules": true, 22 | "noEmitOnError": true, 23 | "noFallthroughCasesInSwitch": true, 24 | "noImplicitOverride": true, 25 | "noImplicitReturns": true, 26 | "noUncheckedIndexedAccess": true, 27 | "noUnusedLocals": true, 28 | "noUnusedParameters": true, 29 | "removeComments": true, 30 | "skipLibCheck": true, 31 | "sourceMap": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vite.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ['tests/**/*.test.ts'], 6 | name: 'node', 7 | environment: 'node', 8 | coverage: { 9 | reporter: ['lcov'], 10 | include: ['src/**/*.ts'], 11 | }, 12 | }, 13 | }) 14 | --------------------------------------------------------------------------------