├── .circleci └── config.yml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── dist ├── LICENSE ├── README.md └── package.json ├── docker-compose.yml ├── e2e ├── __stubs__ │ ├── cat.entity.ts │ ├── cats.service.ts │ ├── config.ts │ └── index.ts ├── couchbase │ ├── couchbase.connection.factory.spec.ts │ └── couchbase.repository.factory.spec.ts └── module │ └── couchbase.module.spec.ts ├── integration ├── app.module.ts ├── couchbase.config.ts ├── https-exception.filter.ts ├── main.ts └── users │ ├── index.ts │ ├── user.entity.ts │ ├── users.controller.ts │ ├── users.module.ts │ └── users.service.ts ├── jest.config.js ├── jest.setup.js ├── package.json ├── scalio-nc.svg ├── src ├── couchbase │ ├── couchbase.connection.factory.ts │ ├── couchbase.constants.ts │ ├── couchbase.repository.factory.ts │ ├── couchbase.repository.mixin.ts │ ├── couchbase.utils.ts │ ├── decorators │ │ ├── entity.decorator.ts │ │ └── index.ts │ ├── exceptions │ │ ├── couchbase.exception.ts │ │ └── index.ts │ ├── index.ts │ └── interfaces │ │ ├── connection-config.interface.ts │ │ ├── index.ts │ │ └── repository.interface.ts ├── index.ts ├── module │ ├── constants.ts │ ├── couchbase-core.module.ts │ ├── couchbase.decorators.ts │ ├── couchbase.module.ts │ ├── index.ts │ ├── interfaces │ │ ├── couchbase-module-async-options.interface.ts │ │ └── index.ts │ ├── providers.ts │ └── utils.ts └── utils.ts ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | references: 2 | master_only: &master_only 3 | filters: 4 | branches: 5 | only: master 6 | versions_only: &versions_only 7 | filters: 8 | tags: 9 | only: /^v.*/ 10 | branches: 11 | ignore: /.*/ 12 | not_master_branch: ¬_master_branch 13 | filters: 14 | branches: 15 | ignore: master 16 | 17 | npm_cache_key: &npm_cache_key 18 | v1-dependency-npm-{{ checksum "yarn.lock" }} 19 | npm_backup_cache_key: &npm_backup_cache_key 20 | v1-dependency-npm 21 | 22 | restore_node_modules: &restore_node_modules 23 | restore_cache: 24 | name: Restore node_modules from cache 25 | keys: 26 | - *npm_cache_key 27 | - *npm_backup_cache_key 28 | 29 | save_node_modules: &save_node_modules 30 | save_cache: 31 | name: Save node_modules to cache 32 | key: *npm_cache_key 33 | paths: 34 | - node_modules 35 | 36 | version: 2 37 | 38 | workflows: 39 | version: 2 40 | # every commit to master is checked 41 | master_lint_test_build: 42 | jobs: 43 | - lint_test_build: 44 | <<: *master_only 45 | # every version (tagged commit) is published 46 | publish: 47 | jobs: 48 | - publish: 49 | <<: *versions_only 50 | context: scalio-npm-registry 51 | # every commit to branch which has PR is checked (CircleCI option 'Only build Pull Requests' should be enabled) 52 | premerge_check: 53 | jobs: 54 | - lint_test_build: 55 | <<: *not_master_branch 56 | 57 | jobs: 58 | 59 | publish: 60 | docker: 61 | - image: node:12 62 | steps: 63 | - checkout 64 | - *restore_node_modules 65 | - run: yarn --frozen-lockfile 66 | - *save_node_modules 67 | - run: yarn build 68 | - run: 69 | name: configure access to NPM registry 70 | command: | 71 | echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" >> ~/.npmrc 72 | - run: yarn publish --new-version ${CIRCLE_TAG#v} dist 73 | 74 | lint_test_build: 75 | docker: 76 | - image: node:12 77 | - image: couchbase:community 78 | steps: 79 | - checkout 80 | - *restore_node_modules 81 | - run: yarn --frozen-lockfile 82 | - *save_node_modules 83 | - run: yarn lint 84 | - run: 85 | name: init couchbase 86 | command: | 87 | curl http://localhost:8091/node/controller/setupServices \ 88 | -d 'services=kv%2Cn1ql%2Cindex%2Cfts' 89 | curl http://localhost:8091/nodes/self/controller/settings \ 90 | -d 'path=%2Fopt%2Fcouchbase%2Fvar%2Flib%2Fcouchbase%2Fdata&index_path=%2Fopt%2Fcouchbase%2Fvar%2Flib%2Fcouchbase%2Fdata' 91 | curl http://localhost:8091/settings/web \ 92 | -d 'password=couchbase&username=couchbase&port=SAME' 93 | - run: yarn test:e2e 94 | - run: yarn build 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | **/.npmrc 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | # Build 65 | dist/* 66 | !dist/package.json 67 | !dist/README.md 68 | !dist/LICENSE 69 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 90, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "arrowParens": "always" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Scal.io, LLC 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 | ![Couchbase for Nest](https://raw.githubusercontent.com/scalio/nest-couchbase/master/scalio-nc.svg?sanitize=true) 2 | 3 |

NestJS Couchbase

4 | 5 |

6 | A Couchbase module for NestJS 7 |

8 | 9 |   10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm i @scalio-oss/nest-couchbase couchbase 15 | ``` 16 | 17 | ## Usage 18 | 19 | `@scalio-oss/nest-couchbase` uses [couchbase](https://www.npmjs.com/package/couchbase) as a data provider and the `Repository` pattern to handle all items (documents) related operations. 20 | 21 | First, let's create an `Entity`: 22 | 23 | ```typescript 24 | import { Entity } from '@scalio-oss/nest-couchbase'; 25 | 26 | @Entity('cats') 27 | export class Cat { 28 | name: string; 29 | } 30 | ``` 31 | 32 | Where `cats` is the Couchbase bucket name (optional). 33 | 34 | Then, we need to import `CouchbaseModule` in our root `AppModule`: 35 | 36 | ```typescript 37 | import { Module } from '@nestjs/common'; 38 | import { CouchbaseModule } from '@scalio-oss/nest-couchbase'; 39 | 40 | @Module({ 41 | imports: [ 42 | CouchbaseModule.forRoot({ 43 | url: 'couchbase://127.0.0.1', 44 | username: 'couchbase', 45 | password: 'couchbase', 46 | defaultBucket: { 47 | name: 'test', 48 | password: 'password', 49 | }, 50 | buckets: [ 51 | { 52 | name: 'another_bucket', 53 | password: 'another_password', 54 | }, 55 | ], 56 | }), 57 | ], 58 | }) 59 | export class AppModule {} 60 | ``` 61 | 62 | In our `CatsModule` we need to initiate repository for our `Cat` entity: 63 | 64 | ```typescript 65 | import { Module } from '@nestjs/common'; 66 | import { CouchbaseModule } from '@scalio-oss/nest-couchbase'; 67 | import { CatsService } from './cats.service'; 68 | import { CatsController } from './cats.controller'; 69 | import { Cat } from './cat.entity'; 70 | 71 | @Module({ 72 | imports: [CouchbaseModule.forFeature([Cat])], 73 | providers: [CatsService], 74 | controllers: [CatsController], 75 | }) 76 | export class CatsModule {} 77 | ``` 78 | 79 | And here is the usage of the repository in the service: 80 | 81 | ```typescript 82 | import { Injectable } from '@nestjs/common'; 83 | import { InjectRepository, Repository } from '@scalio-oss/nest-couchbase'; 84 | import { Cat } from './cat.entity'; 85 | 86 | @Injectable() 87 | export class CatsService { 88 | constructor( 89 | @InjectRepository(Cat) 90 | private readonly catsRepository: Repository, 91 | ) {} 92 | 93 | findOne(id: string): Promise { 94 | return this.catsRepository.get(id); 95 | } 96 | } 97 | ``` 98 | 99 | ## License 100 | 101 | [MIT](LICENSE) 102 | 103 | ## Credits 104 | 105 | Created by [@zMotivat0r](https://github.com/zMotivat0r) @ [Scalio](https://scal.io/) 106 | 107 | ## About us 108 | 109 |

110 |
111 | 112 | 113 | 114 |
115 |

116 | -------------------------------------------------------------------------------- /dist/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Scal.io, LLC 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 | -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | ![Couchbase for Nest](https://raw.githubusercontent.com/scalio/nest-couchbase/master/scalio-nc.svg?sanitize=true) 2 | 3 |

NestJS Couchbase

4 | 5 |

6 | A Couchbase module for NestJS 7 |

8 | 9 |   10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm i @scalio-oss/nest-couchbase couchbase 15 | ``` 16 | 17 | ## Usage 18 | 19 | `@scalio-oss/nest-couchbase` uses [couchbase](https://www.npmjs.com/package/couchbase) as a data provider and the `Repository` pattern to handle all items (documents) related operations. 20 | 21 | First, let's create an `Entity`: 22 | 23 | ```typescript 24 | import { Entity } from '@scalio-oss/nest-couchbase'; 25 | 26 | @Entity('cats') 27 | export class Cat { 28 | name: string; 29 | } 30 | ``` 31 | 32 | Where `cats` is the Couchbase bucket name (optional). 33 | 34 | Then, we need to import `CouchbaseModule` in our root `AppModule`: 35 | 36 | ```typescript 37 | import { Module } from '@nestjs/common'; 38 | import { CouchbaseModule } from '@scalio-oss/nest-couchbase'; 39 | 40 | @Module({ 41 | imports: [ 42 | CouchbaseModule.forRoot({ 43 | url: 'couchbase://127.0.0.1', 44 | username: 'couchbase', 45 | password: 'couchbase', 46 | defaultBucket: { 47 | name: 'test', 48 | password: 'password', 49 | }, 50 | buckets: [ 51 | { 52 | name: 'another_bucket', 53 | password: 'another_password', 54 | }, 55 | ], 56 | }), 57 | ], 58 | }) 59 | export class AppModule {} 60 | ``` 61 | 62 | In our `CatsModule` we need to initiate repository for our `Cat` entity: 63 | 64 | ```typescript 65 | import { Module } from '@nestjs/common'; 66 | import { CouchbaseModule } from '@scalio-oss/nest-couchbase'; 67 | import { CatsService } from './cats.service'; 68 | import { CatsController } from './cats.controller'; 69 | import { Cat } from './cat.entity'; 70 | 71 | @Module({ 72 | imports: [CouchbaseModule.forFeature([Cat])], 73 | providers: [CatsService], 74 | controllers: [CatsController], 75 | }) 76 | export class CatsModule {} 77 | ``` 78 | 79 | And here is the usage of the repository in the service: 80 | 81 | ```typescript 82 | import { Injectable } from '@nestjs/common'; 83 | import { InjectRepository, Repository } from '@scalio-oss/nest-couchbase'; 84 | import { Cat } from './cat.entity'; 85 | 86 | @Injectable() 87 | export class CatsService { 88 | constructor( 89 | @InjectRepository(Cat) 90 | private readonly catsRepository: Repository, 91 | ) {} 92 | 93 | findOne(id: string): Promise { 94 | return this.catsRepository.get(id); 95 | } 96 | } 97 | ``` 98 | 99 | ## License 100 | 101 | [MIT](LICENSE) 102 | 103 | ## Credits 104 | 105 | Created by [@zMotivat0r](https://github.com/zMotivat0r) @ [Scalio](https://scal.io/) 106 | 107 | ## About us 108 | 109 |

110 |
111 | 112 | 113 | 114 |
115 |

116 | -------------------------------------------------------------------------------- /dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scalio-oss/nest-couchbase", 3 | "description": "Couchbase module for Nest framework", 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "typings": "index.d.ts", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/scalio/nest-couchbase.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/scalio/nest-couchbase/issues" 17 | }, 18 | "keywords": [ 19 | "typescript", 20 | "couchbase", 21 | "orm", 22 | "nest", 23 | "nestjs", 24 | "api", 25 | "backend", 26 | "frameworks" 27 | ], 28 | "author": { 29 | "name": "Michael Yali", 30 | "email": "mihon4ik@gmail.com" 31 | }, 32 | "dependencies": { 33 | "@zmotivat0r/o0": "1.0.2" 34 | }, 35 | "peerDependencies": {} 36 | } 37 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | networks: 4 | couchbase-net: 5 | name: couchbase-net 6 | driver: bridge 7 | ipam: 8 | config: 9 | - subnet: 172.16.101.0/24 10 | 11 | volumes: 12 | couchbase1: 13 | 14 | services: 15 | couchbase: 16 | container_name: nest_couchbase 17 | image: couchbase:community 18 | volumes: 19 | - type: volume 20 | source: couchbase1 21 | target: /opt/couchbase1/var 22 | networks: 23 | couchbase-net: 24 | ipv4_address: 172.16.101.11 25 | ports: 26 | - 8091:8091 27 | - 8092:8092 28 | - 8093:8093 29 | - 8094:8094 30 | - 11210:11210 31 | ulimits: 32 | nproc: 65535 33 | core: 34 | soft: 100000000 35 | hard: 100000000 36 | memlock: 37 | soft: 100000000 38 | hard: 100000000 39 | nofile: 40 | soft: 40960 41 | hard: 40960 42 | -------------------------------------------------------------------------------- /e2e/__stubs__/cat.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from '../../src/couchbase'; 2 | 3 | @Entity() 4 | export class Cat { 5 | name: string; 6 | } 7 | -------------------------------------------------------------------------------- /e2e/__stubs__/cats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { InjectRepository, Repository } from '../../src'; 4 | import { Cat } from './cat.entity'; 5 | 6 | @Injectable() 7 | export class CatsService { 8 | constructor(@InjectRepository(Cat) public repo: Repository) {} 9 | } 10 | -------------------------------------------------------------------------------- /e2e/__stubs__/config.ts: -------------------------------------------------------------------------------- 1 | import { CouchbaseConnectionConfig } from '../../src/couchbase'; 2 | 3 | export const config: CouchbaseConnectionConfig = { 4 | url: 'couchbase://127.0.0.1', 5 | username: 'couchbase', 6 | password: 'couchbase', 7 | defaultBucket: { 8 | name: 'e2e_test', 9 | }, 10 | buckets: [ 11 | { 12 | name: 'e2e_test_secure', 13 | password: 'password', 14 | }, 15 | ], 16 | }; 17 | 18 | export const bucketOptions = { bucketType: 'ephemeral', replicaNumber: 0 }; 19 | -------------------------------------------------------------------------------- /e2e/__stubs__/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './cat.entity'; 3 | export * from './cats.service'; 4 | -------------------------------------------------------------------------------- /e2e/couchbase/couchbase.connection.factory.spec.ts: -------------------------------------------------------------------------------- 1 | import { CouchbaseConnectionFactory } from '../../src/couchbase/couchbase.connection.factory'; 2 | import { sleep } from '../../src/utils'; 3 | import { config, bucketOptions } from '../__stubs__'; 4 | 5 | describe('#couchbase', () => { 6 | describe('#CouchbaseConnectionFactory', () => { 7 | it('should be defined', () => { 8 | expect(CouchbaseConnectionFactory).toBeDefined(); 9 | }); 10 | 11 | describe('#create', () => { 12 | it('should be defined', () => { 13 | expect(CouchbaseConnectionFactory.create).toBeDefined(); 14 | }); 15 | it('should create an instance', async () => { 16 | const conn = await CouchbaseConnectionFactory.create(config); 17 | expect(conn).toBeInstanceOf(CouchbaseConnectionFactory); 18 | }); 19 | it('should create an instance with mock', async () => { 20 | const conn = await CouchbaseConnectionFactory.create({ ...config, mock: true }); 21 | expect(conn).toBeInstanceOf(CouchbaseConnectionFactory); 22 | }); 23 | }); 24 | 25 | describe('#methods', () => { 26 | let conn: CouchbaseConnectionFactory; 27 | let mocked: CouchbaseConnectionFactory; 28 | 29 | async function removeBuckets() { 30 | const [_, buckets] = await conn.listBuckets(); 31 | if (buckets && buckets.length) { 32 | for (let i = 0; i < buckets.length; i++) { 33 | await conn.removeBucket(buckets[0].name); 34 | } 35 | } 36 | } 37 | 38 | beforeAll(async () => { 39 | conn = await CouchbaseConnectionFactory.create(config); 40 | mocked = await CouchbaseConnectionFactory.create({ ...config, mock: true }); 41 | await removeBuckets(); 42 | }); 43 | 44 | afterAll(async () => { 45 | await removeBuckets(); 46 | }); 47 | 48 | describe('#createBucket', () => { 49 | it('should create a bucket', async () => { 50 | const [err, ok] = await conn.createBucket( 51 | config.defaultBucket.name, 52 | bucketOptions, 53 | ); 54 | expect(err).toBeUndefined(); 55 | expect(ok).toBe(true); 56 | await sleep(3500); 57 | }); 58 | it('should return an error', async () => { 59 | const [err, _] = await conn.createBucket( 60 | config.defaultBucket.name, 61 | bucketOptions, 62 | ); 63 | expect(err).toBeInstanceOf(Error); 64 | }); 65 | }); 66 | 67 | describe('#listBuckets', () => { 68 | it('should return an array of buckets', async () => { 69 | const [_, buckets] = await conn.listBuckets(); 70 | expect(Array.isArray(buckets)).toBe(true); 71 | }); 72 | }); 73 | 74 | describe('#getBucket', () => { 75 | it('should return an error', async () => { 76 | const [err, _] = await conn.getBucket('invalid'); 77 | expect(err).toBeInstanceOf(Error); 78 | }); 79 | it('should return a bucket', async () => { 80 | const [_, bucket] = await conn.getBucket(config.defaultBucket.name); 81 | expect(bucket).toBeDefined(); 82 | bucket.disconnect(); 83 | }); 84 | it('should return a bucket with mock', async () => { 85 | const [_, bucket] = await mocked.getBucket(config.defaultBucket.name); 86 | expect(bucket).toBeDefined(); 87 | }); 88 | }); 89 | 90 | describe('#removeBucket', () => { 91 | it('should return an error', async () => { 92 | const [err, _] = await conn.removeBucket('invalid'); 93 | expect(err).toBeInstanceOf(Error); 94 | }); 95 | it('should remove a bucket', async () => { 96 | const [_, ok] = await conn.removeBucket(config.defaultBucket.name); 97 | expect(ok).toBe(true); 98 | }); 99 | }); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /e2e/couchbase/couchbase.repository.factory.spec.ts: -------------------------------------------------------------------------------- 1 | import { CouchbaseConnectionFactory } from '../../src/couchbase/couchbase.connection.factory'; 2 | import { CouchbaseRepositoryFactory } from '../../src/couchbase/couchbase.repository.factory'; 3 | import { CouchbaseException } from '../../src/couchbase/exceptions/couchbase.exception'; 4 | import { Entity } from '../../src/couchbase/decorators/entity.decorator'; 5 | import { sleep, flattenPromise } from '../../src/utils'; 6 | import { config, bucketOptions, Cat } from '../__stubs__'; 7 | 8 | describe('#couchbase', () => { 9 | describe('#CouchbaseRepositoryFactory', () => { 10 | it('should be defined', () => { 11 | expect(CouchbaseRepositoryFactory).toBeDefined(); 12 | }); 13 | 14 | let conn: CouchbaseConnectionFactory; 15 | let mocked: CouchbaseConnectionFactory; 16 | 17 | async function removeBuckets() { 18 | const [_, buckets] = await conn.listBuckets(); 19 | if (buckets && buckets.length) { 20 | for (let i = 0; i < buckets.length; i++) { 21 | await conn.removeBucket(buckets[0].name); 22 | } 23 | } 24 | } 25 | 26 | beforeAll(async () => { 27 | conn = await CouchbaseConnectionFactory.create(config); 28 | mocked = await CouchbaseConnectionFactory.create({ ...config, mock: true }); 29 | await removeBuckets(); 30 | await conn.createBucket(config.defaultBucket.name, bucketOptions); 31 | await sleep(3500); 32 | }); 33 | 34 | afterAll(async () => { 35 | const [_, bucket] = await conn.getBucket(config.defaultBucket.name); 36 | bucket.disconnect(); 37 | await removeBuckets(); 38 | }); 39 | 40 | describe('#create', () => { 41 | it('should be defined', () => { 42 | expect(CouchbaseRepositoryFactory.create).toBeDefined(); 43 | }); 44 | it('should throw an error, 1', async () => { 45 | const [err] = await flattenPromise(CouchbaseRepositoryFactory.create)(); 46 | expect(err).toBeInstanceOf(Error); 47 | }); 48 | it('should throw an error, 2', async () => { 49 | const [err] = await flattenPromise(CouchbaseRepositoryFactory.create)(conn); 50 | expect(err).toBeInstanceOf(Error); 51 | }); 52 | it('should throw an error, 4', async () => { 53 | @Entity('invalid') 54 | class InvalidTestEntity {} 55 | const [err] = await flattenPromise(CouchbaseRepositoryFactory.create)( 56 | conn, 57 | InvalidTestEntity, 58 | ); 59 | expect(err).toBeInstanceOf(Error); 60 | }); 61 | it('should create new Repository, 1', async () => { 62 | const repo = await CouchbaseRepositoryFactory.create(conn, Cat); 63 | expect(repo).toBeDefined(); 64 | expect(typeof repo).toBe('object'); 65 | expect(repo.entity).toBeDefined(); 66 | }); 67 | it('should create new Repository, 2', async () => { 68 | class TestEntity {} 69 | const repo = await CouchbaseRepositoryFactory.create(conn, TestEntity); 70 | expect(repo).toBeDefined(); 71 | expect(typeof repo).toBe('object'); 72 | expect(repo.entity).toBeDefined(); 73 | }); 74 | it('should create new Repository with mock', async () => { 75 | const repo = await CouchbaseRepositoryFactory.create(mocked, Cat); 76 | expect(repo).toBeDefined(); 77 | expect(typeof repo).toBe('object'); 78 | expect(repo.entity).toBeDefined(); 79 | }); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /e2e/module/couchbase.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { INestApplication, Injectable, Module, Global } from '@nestjs/common'; 3 | import { CouchbaseError } from 'couchbase'; 4 | 5 | import { CouchbaseConnectionFactory } from '../../src/couchbase/couchbase.connection.factory'; 6 | import { CouchbaseModule } from '../../src/module/couchbase.module'; 7 | import { sleep, flattenPromise } from '../../src/utils'; 8 | import { config, bucketOptions, Cat, CatsService } from '../__stubs__'; 9 | 10 | describe('#module', () => { 11 | describe('#CouchbaseModule', () => { 12 | const msg = 'The key does not exist on the server'; 13 | let app: INestApplication; 14 | let conn: CouchbaseConnectionFactory; 15 | let service: CatsService; 16 | 17 | async function removeBuckets() { 18 | const [_, buckets] = await conn.listBuckets(); 19 | if (buckets && buckets.length) { 20 | for (let i = 0; i < buckets.length; i++) { 21 | await conn.removeBucket(buckets[0].name); 22 | } 23 | } 24 | } 25 | 26 | beforeAll(async () => { 27 | conn = await CouchbaseConnectionFactory.create(config); 28 | await removeBuckets(); 29 | await conn.createBucket(config.defaultBucket.name, bucketOptions); 30 | await sleep(3500); 31 | }); 32 | 33 | afterAll(async () => { 34 | const [_, bucket] = await conn.getBucket(config.defaultBucket.name); 35 | bucket.disconnect(); 36 | await removeBuckets(); 37 | }); 38 | 39 | describe('#forRoot && forFeature', () => { 40 | beforeAll(async () => { 41 | const fixture = await Test.createTestingModule({ 42 | imports: [CouchbaseModule.forRoot(config), CouchbaseModule.forFeature([Cat])], 43 | providers: [CatsService], 44 | }).compile(); 45 | app = fixture.createNestApplication(); 46 | await app.init(); 47 | service = app.get(CatsService); 48 | }); 49 | 50 | afterAll(async () => { 51 | await app.close(); 52 | }); 53 | 54 | it('should throw an error', async () => { 55 | const [err] = await flattenPromise(service.repo.get)('key'); 56 | expect(err.message).toBe(msg); 57 | }); 58 | }); 59 | 60 | describe('#forRootAsync && forFeature', () => { 61 | beforeAll(async () => { 62 | @Injectable() 63 | class ConfigService { 64 | constructor() {} 65 | get() { 66 | return config; 67 | } 68 | } 69 | 70 | @Global() 71 | @Module({ 72 | providers: [ConfigService], 73 | exports: [ConfigService], 74 | }) 75 | class ConfigModule {} 76 | 77 | const fixture = await Test.createTestingModule({ 78 | imports: [ 79 | ConfigModule, 80 | CouchbaseModule.forRootAsync({ 81 | useFactory: (configService: ConfigService) => configService.get(), 82 | inject: [ConfigService], 83 | }), 84 | CouchbaseModule.forFeature([Cat]), 85 | ], 86 | providers: [CatsService], 87 | }).compile(); 88 | app = fixture.createNestApplication(); 89 | await app.init(); 90 | service = app.get(CatsService); 91 | }); 92 | 93 | afterAll(async () => { 94 | await app.close(); 95 | }); 96 | 97 | it('should throw an error', async () => { 98 | const [err] = await flattenPromise(service.repo.get)('key'); 99 | expect(err.message).toBe(msg); 100 | }); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /integration/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { CouchbaseModule } from '../src/module'; 4 | import { UsersModule } from './users'; 5 | import { config } from './couchbase.config'; 6 | 7 | @Module({ 8 | imports: [CouchbaseModule.forRoot(config), UsersModule], 9 | }) 10 | export class AppModule {} 11 | -------------------------------------------------------------------------------- /integration/couchbase.config.ts: -------------------------------------------------------------------------------- 1 | import { CouchbaseConnectionConfig } from '../src/couchbase'; 2 | 3 | export const config: CouchbaseConnectionConfig = { 4 | url: 'couchbase://127.0.0.1', 5 | username: undefined, 6 | password: undefined, 7 | defaultBucket: { 8 | name: 'test', 9 | password: '123456', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /integration/https-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExceptionFilter, 3 | Catch, 4 | ArgumentsHost, 5 | HttpException, 6 | InternalServerErrorException, 7 | } from '@nestjs/common'; 8 | 9 | @Catch() 10 | export class HttpExceptionFilter implements ExceptionFilter { 11 | catch(exception: HttpException, host: ArgumentsHost) { 12 | const ctx = host.switchToHttp(); 13 | const response = ctx.getResponse(); 14 | const { status, json } = this.prepareException(exception); 15 | 16 | response.status(status).send(json); 17 | } 18 | 19 | prepareException(exc: any): { status: number; json: object } { 20 | const error = 21 | exc instanceof HttpException ? exc : new InternalServerErrorException(exc.message); 22 | const status = error.getStatus(); 23 | const response = error.getResponse(); 24 | const json = typeof response === 'string' ? { error: response } : response; 25 | 26 | return { status, json }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /integration/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 3 | 4 | import { HttpExceptionFilter } from './https-exception.filter'; 5 | import { AppModule } from './app.module'; 6 | 7 | async function bootstrap() { 8 | const app = await NestFactory.create(AppModule); 9 | app.useGlobalFilters(new HttpExceptionFilter()); 10 | 11 | const options = new DocumentBuilder() 12 | .setTitle('@scalio/nest-couchbase') 13 | .setDescription('@scalio/nest-couchbase') 14 | .setVersion('1.0') 15 | .build(); 16 | const document = SwaggerModule.createDocument(app, options); 17 | SwaggerModule.setup('docs', app, document); 18 | 19 | await app.listen(process.env.PORT || 3000); 20 | } 21 | 22 | bootstrap(); 23 | -------------------------------------------------------------------------------- /integration/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.entity'; 2 | export * from './users.service'; 3 | export * from './users.module'; 4 | -------------------------------------------------------------------------------- /integration/users/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from '../../src'; 2 | 3 | @Entity('test') 4 | export class User { 5 | name: string; 6 | } 7 | -------------------------------------------------------------------------------- /integration/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param } from '@nestjs/common'; 2 | 3 | import { UsersService } from './users.service'; 4 | 5 | @Controller('users') 6 | export class UsersController { 7 | constructor(private usersService: UsersService) {} 8 | 9 | @Get(':id') 10 | async get(@Param('id') id: string) { 11 | return this.usersService.get(id); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /integration/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { CouchbaseModule } from '../../src'; 4 | import { User } from './user.entity'; 5 | import { UsersService } from './users.service'; 6 | import { UsersController } from './users.controller'; 7 | 8 | @Module({ 9 | imports: [CouchbaseModule.forFeature([User])], 10 | controllers: [UsersController], 11 | providers: [UsersService], 12 | exports: [UsersService], 13 | }) 14 | export class UsersModule {} 15 | -------------------------------------------------------------------------------- /integration/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { InjectRepository, Repository } from '../../src'; 4 | import { User } from './user.entity'; 5 | 6 | @Injectable() 7 | export class UsersService { 8 | constructor(@InjectRepository(User) private repo: Repository) {} 9 | 10 | async get(id: string) { 11 | return (this.repo as any).getFlat(id); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['ts', 'js'], 3 | testRegex: '\\.spec.ts$', 4 | rootDir: '.', 5 | transform: { 6 | '^.+\\.ts$': 'ts-jest', 7 | }, 8 | globals: { 9 | 'ts-jest': { 10 | tsConfig: 'tsconfig.jest.json', 11 | }, 12 | }, 13 | setupFiles: ['./jest.setup.js'], 14 | coverageReporters: ['json', 'lcov', 'text-summary'], 15 | coverageDirectory: 'coverage', 16 | collectCoverageFrom: [ 17 | 'src/**/*.ts', 18 | '!src/**/index.ts', 19 | '!src/**/*.interface.ts', 20 | '!**/node_modules/**', 21 | '!**/__stubs__/**', 22 | '!**/__fixture__/**', 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | require('reflect-metadata'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scalio-oss/nest-couchbase", 3 | "description": "Couchbase module for Nest framework", 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "private": true, 7 | "scripts": { 8 | "prebuild": "yarn build:clean", 9 | "build:clean": "cd dist && rm -rf `ls | grep -v \"LICENSE\\|package.json\\|README.md\\|.npmrc\"`", 10 | "build": "npx tsc -b tsconfig.build.json", 11 | "commit": "npx git-cz", 12 | "lint": "npx tslint 'src/*.ts'", 13 | "format": "npx pretty-quick --pattern 'src/**/*.ts'", 14 | "test:e2e": "npx jest -c=jest.config.js e2e/ --verbose --runInBand", 15 | "start": "npx nodemon -w ./integration -e ts node_modules/.bin/ts-node integration/main.ts" 16 | }, 17 | "husky": { 18 | "hooks": { 19 | "pre-commit": "yarn format --staged", 20 | "commit-msg": "npx validate-commit-msg" 21 | } 22 | }, 23 | "config": { 24 | "commitizen": { 25 | "path": "node_modules/cz-conventional-changelog" 26 | }, 27 | "validate-commit-msg": { 28 | "types": "conventional-commit-types", 29 | "helpMessage": "Use \"yarn commit\" instead" 30 | } 31 | }, 32 | "dependencies": { 33 | "@nestjs/common": "6.5.3", 34 | "@nestjs/core": "6.5.3", 35 | "@nestjs/platform-express": "6.5.3", 36 | "@nestjs/swagger": "^3.1.0", 37 | "@nestjs/testing": "6.5.3", 38 | "@types/couchbase": "2.4.1", 39 | "@types/jest": "24.0.15", 40 | "@types/node": "12.6.8", 41 | "@zmotivat0r/o0": "1.0.2", 42 | "class-transformer": "0.2.3", 43 | "commitizen": "4.0.3", 44 | "couchbase": "2.6.5", 45 | "coveralls": "3.0.5", 46 | "cz-conventional-changelog": "3.0.2", 47 | "husky": "2.7.0", 48 | "jest": "24.8.0", 49 | "nodemon": "^1.19.1", 50 | "prettier": "1.18.2", 51 | "pretty-quick": "1.11.1", 52 | "reflect-metadata": "0.1.13", 53 | "rxjs": "6.5.2", 54 | "swagger-ui-express": "^4.0.7", 55 | "ts-jest": "24.0.2", 56 | "ts-node": "8.3.0", 57 | "tslint": "5.18.0", 58 | "tslint-config-prettier": "1.18.0", 59 | "typescript": "3.5.3", 60 | "validate-commit-msg": "2.14.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /scalio-nc.svg: -------------------------------------------------------------------------------- 1 | header-TEMPLATE -------------------------------------------------------------------------------- /src/couchbase/couchbase.connection.factory.ts: -------------------------------------------------------------------------------- 1 | import { Cluster, ClusterManager, Bucket } from 'couchbase'; 2 | 3 | import { CouchbaseConnectionConfig } from './interfaces'; 4 | import { promisify, flattenPromise } from '../utils'; 5 | 6 | export class CouchbaseConnectionFactory { 7 | config: CouchbaseConnectionConfig; 8 | cluster: Cluster; 9 | manager: ClusterManager; 10 | buckets: { [key: string]: Bucket } = {}; 11 | 12 | constructor(config: CouchbaseConnectionConfig) { 13 | this.config = config; 14 | } 15 | 16 | static async create( 17 | config: CouchbaseConnectionConfig, 18 | ): Promise { 19 | const conn = new CouchbaseConnectionFactory(config); 20 | await conn.createCluster(); 21 | await conn.createManager(); 22 | 23 | return conn; 24 | } 25 | 26 | async getBucket(name: string, password?: string): Promise<[Error, Bucket]> { 27 | return this.config.mock 28 | ? [undefined, this.cluster.openBucket()] 29 | : await this.openBucket(name, password); 30 | } 31 | 32 | async createBucket( 33 | name: string, 34 | /* istanbul ignore next */ options: any = {}, 35 | ): Promise<[Error, boolean]> { 36 | return flattenPromise(promisify(this.manager.createBucket, this.manager))( 37 | name, 38 | options, 39 | ); 40 | } 41 | 42 | async listBuckets(): Promise { 43 | return flattenPromise(promisify(this.manager.listBuckets, this.manager))(); 44 | } 45 | 46 | async removeBucket(name: string): Promise<[Error, boolean]> { 47 | return flattenPromise(promisify(this.manager.removeBucket, this.manager))(name); 48 | } 49 | 50 | private async createCluster(): Promise { 51 | if (this.config.mock) { 52 | const mock = require('couchbase').Mock; 53 | this.cluster = new mock.Cluster(); 54 | } /* istanbul ignore next */ else if (!this.cluster) { 55 | this.cluster = new Cluster(this.config.url, this.config.options); 56 | this.cluster.authenticate(this.config.username, this.config.password); 57 | } 58 | } 59 | 60 | private async createManager(): Promise { 61 | /* istanbul ignore next */ 62 | if (!this.manager) { 63 | this.manager = this.cluster.manager(); 64 | } 65 | } 66 | 67 | private async openBucket(name: string, password?: string): Promise<[Error, Bucket]> { 68 | /* istanbul ignore if */ 69 | if (this.buckets[name]) { 70 | return [undefined, this.buckets[name]]; 71 | } 72 | 73 | return flattenPromise( 74 | () => 75 | new Promise((resolve, reject) => { 76 | const bucket = this.cluster.openBucket.bind(this.cluster)( 77 | name, 78 | password, 79 | (err: Error) => { 80 | if (err) { 81 | reject(err); 82 | } else { 83 | this.buckets[name] = bucket; 84 | resolve(bucket); 85 | } 86 | }, 87 | ); 88 | }), 89 | )(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/couchbase/couchbase.constants.ts: -------------------------------------------------------------------------------- 1 | export const COUCHBASE_ENTITY_METADATA = 'COUCHBASE_ENTITY_METADATA'; 2 | -------------------------------------------------------------------------------- /src/couchbase/couchbase.repository.factory.ts: -------------------------------------------------------------------------------- 1 | import { Repository, CouchbaseBucketConfig } from './interfaces'; 2 | import { CouchbaseConnectionFactory } from './couchbase.connection.factory'; 3 | import { CouchbaseRepositoryMixin } from './couchbase.repository.mixin'; 4 | import { getEntityMetadata } from './couchbase.utils'; 5 | 6 | export class CouchbaseRepositoryFactory { 7 | static async create( 8 | conn: CouchbaseConnectionFactory, 9 | entity: T, 10 | ): Promise> { 11 | const { name, password } = CouchbaseRepositoryFactory.getBucketConfig(conn, entity); 12 | const [err, bucket] = await conn.getBucket(name, password); 13 | if (err) { 14 | throw err; 15 | } 16 | return new (CouchbaseRepositoryMixin(bucket, entity))(); 17 | } 18 | 19 | private static getBucketConfig( 20 | conn: CouchbaseConnectionFactory, 21 | entity: any, 22 | ): CouchbaseBucketConfig { 23 | const name = getEntityMetadata(entity) || conn.config.defaultBucket.name; 24 | let password: string; 25 | 26 | if (name === conn.config.defaultBucket.name) { 27 | password = conn.config.defaultBucket.password; 28 | } else if (Array.isArray(conn.config.buckets) && conn.config.buckets.length) { 29 | const bucketConfig = conn.config.buckets.find((one) => one.name === name); 30 | 31 | if (bucketConfig) { 32 | password = bucketConfig.password; 33 | } 34 | } 35 | 36 | return { name, password }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/couchbase/couchbase.repository.mixin.ts: -------------------------------------------------------------------------------- 1 | import { Bucket } from 'couchbase'; 2 | import * as BucketClass from 'couchbase/lib/bucket'; 3 | 4 | import { promisify, flattenPromise } from '../utils'; 5 | import { Repository } from './interfaces'; 6 | 7 | export function CouchbaseRepositoryMixin(bucket: Bucket, entity: T): Repository { 8 | class CouchbaseRepository { 9 | entity: T; 10 | 11 | constructor() { 12 | this.entity = entity; 13 | } 14 | } 15 | 16 | Object.assign(CouchbaseRepository.prototype, BucketClass.prototype); 17 | Object.getOwnPropertyNames(BucketClass.prototype).forEach((name: string) => { 18 | if ( 19 | name !== 'constructor' && 20 | typeof CouchbaseRepository.prototype[name] === 'function' 21 | ) { 22 | const method = promisify(BucketClass.prototype[name], bucket); 23 | CouchbaseRepository.prototype[name] = method; 24 | CouchbaseRepository.prototype[`${name}Flat`] = flattenPromise(method); 25 | } 26 | }); 27 | Object.defineProperty(CouchbaseRepository, 'name', { 28 | writable: false, 29 | value: `Generated${(entity as any).name}CouchbaseRepository`, 30 | }); 31 | 32 | return CouchbaseRepository as any; 33 | } 34 | -------------------------------------------------------------------------------- /src/couchbase/couchbase.utils.ts: -------------------------------------------------------------------------------- 1 | import { COUCHBASE_ENTITY_METADATA } from './couchbase.constants'; 2 | 3 | export const getEntityMetadata = (entity: T): string => 4 | Reflect.getMetadata(COUCHBASE_ENTITY_METADATA, entity); 5 | -------------------------------------------------------------------------------- /src/couchbase/decorators/entity.decorator.ts: -------------------------------------------------------------------------------- 1 | import { COUCHBASE_ENTITY_METADATA } from '../couchbase.constants'; 2 | 3 | export const Entity = (bucket?: string) => (target: Object) => { 4 | Reflect.defineMetadata(COUCHBASE_ENTITY_METADATA, bucket, target); 5 | }; 6 | -------------------------------------------------------------------------------- /src/couchbase/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './entity.decorator'; 2 | -------------------------------------------------------------------------------- /src/couchbase/exceptions/couchbase.exception.ts: -------------------------------------------------------------------------------- 1 | export class CouchbaseException extends Error { 2 | constructor(msg?: string) { 3 | super(msg); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/couchbase/exceptions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './couchbase.exception'; 2 | -------------------------------------------------------------------------------- /src/couchbase/index.ts: -------------------------------------------------------------------------------- 1 | export * from './interfaces'; 2 | export * from './decorators'; 3 | export * from './couchbase.constants'; 4 | export * from './couchbase.connection.factory'; 5 | export * from './couchbase.repository.factory'; 6 | export * from './couchbase.repository.mixin'; 7 | -------------------------------------------------------------------------------- /src/couchbase/interfaces/connection-config.interface.ts: -------------------------------------------------------------------------------- 1 | import { ClusterConstructorOptions } from 'couchbase'; 2 | 3 | export interface CouchbaseConnectionConfig { 4 | url: string; 5 | username?: string; 6 | password?: string; 7 | defaultBucket: CouchbaseBucketConfig; 8 | buckets?: CouchbaseBucketConfig[]; 9 | sync?: boolean; 10 | options?: ClusterConstructorOptions; 11 | mock?: boolean; 12 | } 13 | 14 | export interface CouchbaseBucketConfig { 15 | name: string; 16 | password?: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/couchbase/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './connection-config.interface'; 2 | export * from './repository.interface'; 3 | -------------------------------------------------------------------------------- /src/couchbase/interfaces/repository.interface.ts: -------------------------------------------------------------------------------- 1 | import { AppendOptions, CounterOptions, GetAndLockOptions } from 'couchbase'; 2 | 3 | export interface Repository { 4 | new (): Repository; 5 | entity: T; 6 | 7 | /** 8 | * Similar to Bucket#upsert, but instead of setting a new key, it appends data to the existing key. Note that this function only makes sense when the stored data is a string; 'appending' to a JSON document may result in parse errors when the document is later retrieved. 9 | * @param key The target document key. 10 | * @param fragment The document's contents to append. 11 | */ 12 | append(key: string | Buffer, fragment: Partial): Promise; 13 | 14 | /** 15 | * 16 | * @param key The target document key. 17 | * @param fragment The document's contents to append. 18 | * @param options The options object. 19 | * @param callback The callback function. 20 | */ 21 | append( 22 | key: string | Buffer, 23 | fragment: Partial, 24 | options: AppendOptions, 25 | ): Promise; 26 | 27 | /** 28 | * Increments or decrements a key's numeric value. 29 | * Note that JavaScript does not support 64-bit integers (while lib couchbase and the server do). You might receive an inaccurate value if the number is greater than 53-bits (JavaScript's maximum integer precision). 30 | * @param key The target document key. 31 | * @param delta The amount to add or subtract from the counter value. This value may be any non-zero integer. 32 | */ 33 | counter(key: string | Buffer, delta: number): Promise; 34 | 35 | /** 36 | * 37 | * @param key The target document key. 38 | * @param delta The amount to add or subtract from the counter value. This value may be any non-zero integer. 39 | * @param options The options object. 40 | */ 41 | counter(key: string | Buffer, delta: number, options: CounterOptions): Promise; 42 | 43 | /** 44 | * Retrieves a document. 45 | * @param key The target document key. 46 | */ 47 | get(key: string | Buffer): Promise; 48 | 49 | /** 50 | * @param key The target document key. 51 | * @param options The options object. 52 | */ 53 | get(key: string | Buffer, options: any): Promise; 54 | 55 | /** 56 | * Lock the document on the server and retrieve it. When an document is locked, its CAS changes and subsequent operations on the document (without providing the current CAS) will fail until the lock is no longer held. 57 | * This function behaves identically to Bucket#get in that it will return the value. It differs in that the document is also locked. This ensures that attempts by other client instances to access this document while the lock is held will fail. 58 | * Once locked, a document can be unlocked either by explicitly calling Bucket#unlock or by performing a storage operation (e.g. Bucket#upsert, Bucket#replace, Bucket::append) with the current CAS value. Note that any other lock operations on this key will fail while a document is locked. 59 | * @param key The target document key. 60 | */ 61 | getAndLock(key: string): Promise; 62 | 63 | /** 64 | * Lock the document on the server and retrieve it. When an document is locked, its CAS changes and subsequent operations on the document (without providing the current CAS) will fail until the lock is no longer held. 65 | * This function behaves identically to Bucket#get in that it will return the value. It differs in that the document is also locked. This ensures that attempts by other client instances to access this document while the lock is held will fail. 66 | * Once locked, a document can be unlocked either by explicitly calling Bucket#unlock or by performing a storage operation (e.g. Bucket#upsert, Bucket#replace, Bucket::append) with the current CAS value. Note that any other lock operations on this key will fail while a document is locked. 67 | * @param key The target document key. 68 | * @param options The options object. 69 | * @returns {} 70 | */ 71 | getAndLock(key: string, options: GetAndLockOptions): Promise; 72 | 73 | /** 74 | * Retrieves a document and updates the expiry of the item at the same time. 75 | * @param key The target document key. 76 | * @param expiry The expiration time to use. If a value of 0 is provided, then the current expiration time is cleared and the key is set to never expire. Otherwise, the key is updated to expire in the time provided (in seconds). 77 | * @param options The options object. 78 | */ 79 | getAndTouch(key: string | Buffer, expiry: number, options: any): Promise; 80 | 81 | /** 82 | * Retrieves a document and updates the expiry of the item at the same time. 83 | * @param key The target document key. 84 | * @param expiry The expiration time to use. If a value of 0 is provided, then the current expiration time is cleared and the key is set to never expire. Otherwise, the key is updated to expire in the time provided (in seconds). 85 | */ 86 | getAndTouch(key: string | Buffer, expiry: number): Promise; 87 | } 88 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './module'; 2 | export * from './couchbase'; 3 | -------------------------------------------------------------------------------- /src/module/constants.ts: -------------------------------------------------------------------------------- 1 | export const COUCHBASE_CONNECTION_TOKEN = 'COUCHBASE_CONNECTION_TOKEN'; 2 | export const COUCHBASE_MODULE_OPTIONS = 'COUCHBASE_MODULE_OPTIONS'; 3 | -------------------------------------------------------------------------------- /src/module/couchbase-core.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Global, 3 | Module, 4 | DynamicModule, 5 | Provider, 6 | OnApplicationShutdown, 7 | } from '@nestjs/common'; 8 | 9 | import { CouchbaseConnectionConfig, CouchbaseConnectionFactory } from '../couchbase'; 10 | import { CouchbaseModuleAsyncOptions } from './interfaces'; 11 | import { 12 | createCouchbaseConnectionProviders, 13 | createCouchbaseAsyncConnectionProviders, 14 | } from './providers'; 15 | import { InjectConnection } from './couchbase.decorators'; 16 | 17 | @Global() 18 | @Module({}) 19 | export class CouchbaseCoreModule implements OnApplicationShutdown { 20 | constructor(@InjectConnection() private conn: CouchbaseConnectionFactory) {} 21 | 22 | onApplicationShutdown() { 23 | Object.keys(this.conn.buckets).forEach((bucket) => 24 | this.conn.buckets[bucket].disconnect(), 25 | ); 26 | } 27 | 28 | static forRoot(config: CouchbaseConnectionConfig): DynamicModule { 29 | const providers = createCouchbaseConnectionProviders(config); 30 | return CouchbaseCoreModule.outputDynamicModule(providers); 31 | } 32 | 33 | static forRootAsync(options: CouchbaseModuleAsyncOptions): DynamicModule { 34 | const providers = createCouchbaseAsyncConnectionProviders(options); 35 | return CouchbaseCoreModule.outputDynamicModule(providers); 36 | } 37 | 38 | private static outputDynamicModule(providers: Provider[]): DynamicModule { 39 | return { 40 | module: CouchbaseCoreModule, 41 | providers, 42 | exports: providers, 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/module/couchbase.decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | 3 | import { getConnectionToken, getRepositoryToken } from './utils'; 4 | 5 | export const InjectRepository = (entity: Function): ParameterDecorator => 6 | Inject(getRepositoryToken(entity)); 7 | 8 | export const InjectConnection = (): ParameterDecorator => Inject(getConnectionToken()); 9 | -------------------------------------------------------------------------------- /src/module/couchbase.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, DynamicModule } from '@nestjs/common'; 2 | 3 | import { CouchbaseConnectionConfig } from '../couchbase'; 4 | import { CouchbaseModuleAsyncOptions } from './interfaces'; 5 | import { CouchbaseCoreModule } from './couchbase-core.module'; 6 | import { createCouchbaseProviders } from './providers'; 7 | 8 | @Module({}) 9 | export class CouchbaseModule { 10 | static forRoot(config: CouchbaseConnectionConfig): DynamicModule { 11 | return { 12 | module: CouchbaseModule, 13 | imports: [CouchbaseCoreModule.forRoot(config)], 14 | }; 15 | } 16 | 17 | static forRootAsync(options: CouchbaseModuleAsyncOptions): DynamicModule { 18 | return { 19 | module: CouchbaseModule, 20 | imports: [CouchbaseCoreModule.forRootAsync(options)], 21 | }; 22 | } 23 | 24 | static forFeature(entities: Function[]): DynamicModule { 25 | const providers = createCouchbaseProviders(entities); 26 | return { 27 | module: CouchbaseModule, 28 | providers, 29 | exports: providers, 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/module/index.ts: -------------------------------------------------------------------------------- 1 | export * from './couchbase.module'; 2 | export * from './utils'; 3 | export * from './couchbase.decorators'; 4 | -------------------------------------------------------------------------------- /src/module/interfaces/couchbase-module-async-options.interface.ts: -------------------------------------------------------------------------------- 1 | import { CouchbaseConnectionConfig } from '../../couchbase/interfaces'; 2 | 3 | export interface CouchbaseModuleAsyncOptions { 4 | useFactory: ( 5 | ...args: any[] 6 | ) => Promise | CouchbaseConnectionConfig; 7 | inject?: any[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/module/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './couchbase-module-async-options.interface'; 2 | -------------------------------------------------------------------------------- /src/module/providers.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@nestjs/common'; 2 | 3 | import { 4 | CouchbaseConnectionFactory, 5 | CouchbaseRepositoryFactory, 6 | CouchbaseConnectionConfig, 7 | } from '../couchbase'; 8 | import { CouchbaseModuleAsyncOptions } from './interfaces'; 9 | import { getConnectionToken, getModuleOptionsToken, getRepositoryToken } from './utils'; 10 | 11 | export const createCouchbaseConnectionProviders = ( 12 | config: CouchbaseConnectionConfig, 13 | ): Provider[] => [ 14 | { 15 | provide: getConnectionToken(), 16 | useFactory: async () => CouchbaseConnectionFactory.create(config), 17 | }, 18 | ]; 19 | 20 | export const createCouchbaseAsyncConnectionProviders = ( 21 | options: CouchbaseModuleAsyncOptions, 22 | ): Provider[] => [ 23 | { 24 | provide: getModuleOptionsToken(), 25 | useFactory: options.useFactory, 26 | inject: options.inject || [], 27 | }, 28 | { 29 | provide: getConnectionToken(), 30 | useFactory: async (config: CouchbaseConnectionConfig) => 31 | CouchbaseConnectionFactory.create(config), 32 | inject: [getModuleOptionsToken()], 33 | }, 34 | ]; 35 | 36 | export const createCouchbaseRepositoryProvider = (entity: Function): Provider => ({ 37 | provide: getRepositoryToken(entity), 38 | useFactory: async (conn: CouchbaseConnectionFactory) => 39 | CouchbaseRepositoryFactory.create(conn, entity), 40 | inject: [getConnectionToken()], 41 | }); 42 | 43 | export const createCouchbaseProviders = (entities: Function[]): Provider[] => 44 | entities.map(createCouchbaseRepositoryProvider); 45 | -------------------------------------------------------------------------------- /src/module/utils.ts: -------------------------------------------------------------------------------- 1 | import { COUCHBASE_CONNECTION_TOKEN, COUCHBASE_MODULE_OPTIONS } from './constants'; 2 | 3 | export const getConnectionToken = (): string => COUCHBASE_CONNECTION_TOKEN; 4 | export const getModuleOptionsToken = (): string => COUCHBASE_MODULE_OPTIONS; 5 | export const getRepositoryToken = (entity: Function): string => 6 | `${entity.name}_REPOSITORY_TOKEN`; 7 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { oO } from '@zmotivat0r/o0'; 2 | 3 | export function promisify(fn: any, ctx: any): any { 4 | return function(...args: any[]): Promise { 5 | return new Promise((resolve, reject) => { 6 | fn.apply(ctx, [ 7 | ...args, 8 | function(err: Error, ...rest: any[]) { 9 | if (err) { 10 | reject(err); 11 | } else { 12 | resolve(rest.length > 1 ? rest : rest[0]); 13 | } 14 | }, 15 | ]); 16 | }); 17 | }; 18 | } 19 | 20 | export function flattenPromise(promise: any): any { 21 | return async function(...args: any[]): Promise { 22 | return oO(promise(...args)); 23 | }; 24 | } 25 | 26 | export async function sleep(time: number) { 27 | return new Promise((resolve) => { 28 | setTimeout(resolve, time); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src"], 8 | "exclude": ["node_modules", "test"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "removeComments": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "noUnusedLocals": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es2017", 12 | "sourceMap": true, 13 | "allowJs": false, 14 | "skipLibCheck": false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:latest", "tslint-config-prettier"], 4 | "jsRules": {}, 5 | "rules": { 6 | "object-literal-sort-keys": false, 7 | "member-access": false, 8 | "no-implicit-dependencies": false, 9 | "member-ordering": false, 10 | "only-arrow-functions": false 11 | }, 12 | "rulesDirectory": [] 13 | } 14 | --------------------------------------------------------------------------------