├── .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 | 
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 | 
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 |
--------------------------------------------------------------------------------