Skip to content

Commit

Permalink
Merge pull request #56 from paralect/v3
Browse files Browse the repository at this point in the history
Update node-mongo
  • Loading branch information
KuhArt authored Apr 7, 2022
2 parents ab20eb1 + 1c13e91 commit bb05019
Show file tree
Hide file tree
Showing 40 changed files with 3,195 additions and 2,176 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
45 changes: 40 additions & 5 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
module.exports = {
extends: 'airbnb-base',
root: true,
parser: "@typescript-eslint/parser",
plugins: [
"@typescript-eslint"
],
extends: [
"airbnb-typescript/base",
"plugin:@typescript-eslint/recommended"
],
parserOptions: {
ecmaVersion: 2018,
sourceType: "module",
project: "./tsconfig.json",
// use tsconfig relative to eslintrc file for IDE
tsconfigRootDir: __dirname
},
rules: {
'no-underscore-dangle': 0,
// mongodb has _id
"no-underscore-dangle": "off",
"no-param-reassign": "off",
"import/prefer-default-export": "warn"
},
env: {
mocha: true,
settings: {
"import/resolver": {
"node": {
"moduleDirectory": [
"src",
"node_modules"
]
}
}
},
};
ignorePatterns: [
// ignore this file
".eslintrc.js",
// never lint node modules
"node_modules",
// ignore prod_node_modules copied in Docker
"prod_node_modules",
// ignore output build files
"dist"
]
}
4 changes: 2 additions & 2 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 14
- run: npm ci

publish-npm:
Expand All @@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 14
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
.idea/
coverage
.DS_Store
dist/
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## v3.0.0 (2022-03-28)

The release includes a lot of changes to make sure that the package is compatible with the latest MongoDB version.
Most notable changes:
- Rewritten in typescript
- Removed [monk](https://github.com/Automattic/monk) dependency.
- Added [mongodb native Node.JS sdk](https://www.mongodb.com/docs/drivers/node/current/) as dependency.
- Added support for transactional events using [transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html)
- Introduced shared in-memory events bus. It should be used to listen for CUD updates.

## v2.1.0 (2020-10-15)

### Features
Expand Down
11 changes: 11 additions & 0 deletions Dockerfile.tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM node:16.13.1-alpine3.13

WORKDIR /app
COPY ["package*.json", "/app/"]
RUN npm ci --quiet

COPY . .

RUN npm run build

CMD npm run all
181 changes: 137 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,87 @@ npm i @paralect/node-mongo

## Documentation

[API Reference](API.md).
### Migrate from v2 to v3

## Usage
1. Methods `updateOne` and `updateMany` were removed. You should use `update` to perform update of single document, matched by a query. There is no replacement for `updateMany`, normally you should just perform multiple individual updates.
2. `service.count()` renamed into `service.countDocuments` to match MongoDB driver.
3. Use `service.atomic.updateMany` instead `service.atomic.update` to match MongoDB.
4. `service.aggregate()` now returns cursor instead of list of documents. You can add `toArray()`
5. Service accepts `schema` object instead of `validateSchema` method.

### Connect to MongoDB
```javascript
const connectionString = 'mongodb://localhost:27017/home-db';
const db = require('@paralect/node-mongo').connect(connectionString);
### Connect

Usually, you need to define a file called `database` is does two things:
1. Creates database instance and connects to the database
2. Exposed factory method `createService` to create different services to work with MongoDB.

```typescript
import config from 'config';
import { Database, Service, ServiceOptions } from '@paralect/node-mongo';

const connectionString = 'mongodb://localhost:27017';
const dbName = 'home-db';
const database = new Database(connectionString, dbName);
database.connect();

// Extended service can be used here.
function createService<T>(collectionName: string, options: ServiceOptions = {}) {
return new Service<T>(collectionName, database, options);
}

export default {
database,
createService,
};
```

### CRUD Operations
See [how to add additional functionality to base serivce](#extend)


### Schema validation
```javascript
// create a service to work with specific database collection
const userService = db.createService('users');
const Joi = require('Joi');

// create documents
const users = await userService.create([
{ name: 'Alex' },
{ name: 'Bob' },
]);
const userSchema = Joi.object({
_id: Joi.string(),
createdOn: Joi.date(),
updatedOn: Joi.date(),
deletedOn: Joi.date(),
name: Joi.string(),
status: Joi.string().valid('active', 'inactive'),
});

// Pass schema object to enable schema validation
const userService = db.createService('users', { schema: userSchema });
```

### Extend

The whole idea is to import service and extend it with custom methods:

```typescript
import { Service } from '@paralect/node-mongo';

class CustomService<T> extends Service<T> {
createOrUpdate = async (query: any, updateCallback: (item?: T) => Partial<T>) => {
const docExists = await this.exists(query);
if (!docExists) {
const newDoc = updateCallback();
return this.create(newDoc);
}

return this.update(query, doc => {
return updateCallback(doc);
});
};
}

export default CustomService;
```

### Query data

```typescript
// find one document
const user = await userService.findOne({ name: 'Bob' });

Expand All @@ -48,50 +108,83 @@ const {results, pagesCount, count } = await userService.find(
{ name: 'Bob' },
{ page: 1, perPage: 30 },
);
```

// update document
const updatedUser = await userService.updateOne(
{ _id: '1' },
(doc) => ({ ...doc, name: 'Alex' }),
);
### Create or update data (and publish CUD events)

// remove document
const removedUser = await userService.remove({ _id: '1' });
The key difference of the `@paralect/node-mongo` sdk is that every create, update or remove operation peforms
an udpate and also publeshes CUD event. Events are used to easily update denormalized data and also to implement
complex business logic without tight coupling of different entities.

- Reactive updates (every update publishes event)
- [create](#create) — create one or many documents, publishes `document.created` event
- [update](#update) — update one document, publishes `document.updated` event
- [remove](#remove) — remove document, publishes `document.removed`
- [removeSoft](#removeSoft) — set `deleteOn` field and publish `document.removed` event

Atomic udpates **do not publish events** and usually used to update denormalized data. Most the time you should be using reactive updates.

- Atomic updates (events are not published)
- `atomic.deleteMany`
- `atomic.insertMany`
- `atomic.updateMany`
- `findOneAndUpdate`

[API Reference V2](API.md).

#### create

```typescript
const users = await userService.create([
{ name: 'Alex' },
{ name: 'Bob' },
]);
```

### Event handlers
```js
const userService = db.createService('users');
#### update

userService.on('created', ({ doc }) => {
Update using callback function:
```typescript
const updatedUser = await userService.update({ _id: '1' }, (doc) => {
doc.name = 'Alex';
});
```

userService.on('updated', ({ doc, prevDoc }) => {
});
Update by returning fields you need to update:
```typescript
const updatedUser = await userService.update({ _id: '1' }, () => ({ name: 'Alex' }));
```

userService.onPropertiesUpdated(['email'], ({ doc, prevDoc }) => {
});
### remove
```typescript
const removedUser = await userService.remove({ _id: '1' });
```

userService.on('removed', ({ doc }) => {
});
### removeSoft
```typescript
const removedUser = await userService.removeSoft({ _id: '1' });
```

### Schema validation
```javascript
const Joi = require('Joi');
### Event handlers

const userSchema = Joi.object({
_id: Joi.string(),
createdOn: Joi.date(),
name: Joi.string(),
status: Joi.string().valid('active', 'inactive'),
});
SDK support two kind of events:
- `in memory events` (published by default), can be lost on service failure, work out of the box.
- `transactional events` guarantee that every database write will also produce an event. Transactional events can be enabled by setting `{ outbox: true }` when creating service. Transactional events require additonal infrastructure components.

function validate(obj) {
return userSchema.validate(obj);
}
To subscribe to the in memory events you can just do following:

```typescript
import { inMemoryEventBus, InMemoryEvent } from '@paralect/node-mongo';

type UserCreatedType = InMemoryEvent<any>;
type UserUpdatedType = InMemoryEvent<any>;
type UserRemovedType = InMemoryEvent<any>;

inMemoryEventBus.on('user.created', (doc: UserCreatedType) => {});

inMemoryEventBus.on('user.updated', (doc: UserUpdatedType) => {});

const userService = db.createService('users', { validate });
inMemoryEventBus.on('user.removed', (doc: UserRemovedType) => {});
```

## Change Log
Expand Down
2 changes: 2 additions & 0 deletions bin/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
docker-compose -f docker-compose.tests.yml up --build
52 changes: 52 additions & 0 deletions docker-compose.tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
version: '3.6'
services:
mongo:
container_name: mongo
image: mongo:4.4
command: --replSet rs --bind_ip_all --keyFile /mongo-replicator/keyfile
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
networks:
- node-mongo-tests
ports:
- 27017:27017
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./mongo-replicator:/mongo-replicator
- type: volume
source: mongodb
target: /data/db
- type: volume
source: mongodb-cfg
target: /data/configdb
mongo-replicator:
container_name: mongo-replicator
build: ./mongo-replicator
environment:
- HOST=mongo
- PORT=27017
- USERNAME=root
- PASSWORD=root
- REPLICA_SET_NAME=rs
networks:
- node-mongo-tests
depends_on:
- mongo
tests:
container_name: tests
build:
context: .
dockerfile: ./Dockerfile.tests
networks:
- node-mongo-tests
depends_on:
- mongo-replicator

networks:
node-mongo-tests:
name: node-mongo-tests-network

volumes:
mongodb:
mongodb-cfg:
3 changes: 3 additions & 0 deletions mongo-replicator/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM mongo:4.4
ADD ./setup.sh /setup.sh
CMD ["/setup.sh"]
16 changes: 16 additions & 0 deletions mongo-replicator/keyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
eATfKkkUG4tSqfr3dJiRruI6QBYBzxhBBMVu4f8IoMQ8Hmf9GC9/6/TRvdk7mt4p
z8MccxDRBqGgpAoMOyosUGZxprnJjzNSRKgofRgT9YE4mopMGB4nsRMRS+PR5o6v
YlUSKrK/rrsKF+ogBKn350S1328aBaucjEuajsL5lMlt+wuqRvRVo4SzPizLhXuv
W2Y31+7Yex4V8AmEfs5h0wPDEzsdAdifgW4Io6A6pX17tJkVRW+DkLYNEu9laquF
L6+S5vFYO7Rv85GhBeSFGSgj0Sqchj3UhkPoy7R17nKfAmeF0m6H1WEPw0iFk/bk
E2y+9r/dpVuRX/NcyosXGa6RMs622hVU+Bi9Kme52mRm1LQJAPFhgZYMAYNUt3S8
ygT2iLJSyWx68MFtYGoKHcH0ISWo/2ouzZa7tt2+BzQXylfWhQrE1xJl+XcFfeei
qEZcC9bZ/za3eoWODPjHRCfp+uL4OEtztZ++S9n2eLmP2VyviUotvL0vnBR2GNEW
5DIqCa/UZnuGaFbHqbmfoRqrl0E6cH3AvrEXWWkC42nshCv+rHnFu15v2bG5z8nF
0UMUyXn7lcLqLYYwwrv3RQrfdNLImBu+JNBH/BRS7ZI6IsrU90rn+t+9xq1N/yvs
dS2ujZRskroTX6hDh9PeCBPoOcEjDldGEt0aWkzQWFSgk+BpOv6UO5CzVUacIVa+
xmjm16ksC0WsoPzxiOiM/4ChbcNCOOdErqQsnptAS5/kzb2r/CZTyDgTd1REiQR3
PbiLWWx5C+p+tzdeI4ueyDjuriaiKZqsS+Hv7qmy1UAra8ZzOEzdYYv+pueLNZrf
Q7+DU4uNEBbT3EOR6+T72+NVnid+Zq1z3Apo83zQEcMHAUhUQIKNYv8mUV89cxSE
3DUEVaovguI42qpmeYeBbURxyhrBsI2bnpw42rfGIClZRc7dK67SP9XYDtrz/990
LG6V0OVD7Mt6h35pkdvSLNJhFJmHYFVLqluHJdlb9ui3mHv5
Loading

0 comments on commit bb05019

Please sign in to comment.