https://github.com/mkeen/rxcouch
π (Universal) CouchDB Client Powered by ReactiveX (RxJS)
https://github.com/mkeen/rxcouch
angular angular6 couchdb couchdb-client javascript javascript-library npm-module rxjs rxjs6 typescript
Last synced: 11 months ago
JSON representation
π (Universal) CouchDB Client Powered by ReactiveX (RxJS)
- Host: GitHub
- URL: https://github.com/mkeen/rxcouch
- Owner: mkeen
- License: isc
- Created: 2018-07-19T05:06:46.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2023-03-04T02:45:11.000Z (about 3 years ago)
- Last Synced: 2025-05-08T21:58:44.473Z (11 months ago)
- Topics: angular, angular6, couchdb, couchdb-client, javascript, javascript-library, npm-module, rxjs, rxjs6, typescript
- Language: TypeScript
- Homepage:
- Size: 1.74 MB
- Stars: 7
- Watchers: 1
- Forks: 1
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# RxCouch
`π Universal`
Real-time CouchDB client that runs in the browser or nodejs. The change feed is handled automatically. Everything's a `BehaviorSubject`, so you get two-way binding for free.
## Prerequisites
RxJS 6+
CouchDB 2.3+, CouchDB 3.0+
## Installation
**NPM:** `npm install @mkeen/rxcouch`
**Yarn:** `yarn install @mkeen/rxcouch`
## Developer Documentation
### Including RxCouch in Your Project
```typescript
import { CouchDB } from '@mkeen/rxcouch';
```
### Initialize RxCouch for Connecting to a CouchDB Database
```typescript
const couchDbConnection = new CouchDB(
{
dbName: 'people',
}
);
```
### Basic Configuration Options
CouchDB is initialized with: `(RxCouchConfig, AuthorizationBehavior?, Observable?)`
An `RxCouchConfig` looks like this by default:
{ `host`: 'localhost'
`port`: 5984
`ssl`: false
`trackChanges`: true,
`dbName`: '_users' }
### Configuring Authentication
CouchDB supports [Basic Auth, Cookies, and JWT](https://docs.couchdb.org/en/stable/api/server/authn.html) authentication schemes. This library supports Cookies or JWT (bearer token). For development purposes only, this library also supports open installs of couchdb without security.
Let's take a look at how to initialize RxCouch for connecting to a CouchDB database that has Cookie Authentication configured.
```typescript
import { BehaviorSubject } from 'rxjs';
import { CouchDB,
CouchSession,
AuthorizationBehavior } from '@mkeen/rxcouch';
const couchSession: CouchSession = new CouchSession(
AuthorizationBehavior.cookie,
`${COUCH_SSL? 'https://' : 'http://'}${COUCH_HOST}:${COUCH_PORT}/_session`,
new BehaviorSubject({username: 'username', password: 'password'}}),
);
const couchDbConnection = new CouchDB(
{
dbName: 'people',
host: 'localhost',
trackChanges: true,
},
couchSession
);
//...
```
The big detail to note here is that the final argument passed to the CouchSession initializer is an `Observable`. In this example it's hardcoded. In a real-world implementation, you'll create an `Observable` that emits when a user submits a login form, or when a configuration value is read, and pass that `Observable` into the initializer.
Since we're hardcoding the credentials `Observable` argument, the above example will result in an authentication attempt being made to the speficied CouchDB host (without using HTTPS) immediately.
### Getting Info About the Current Session
To determine which user (if any) is currently logged into CouchDB, you can call the `session()` function. `session` returns an `Observable` that emits a `CouchDBSession`.
```typescript
//...
couchDbConnection.session().subscribe((session: CouchDBSession) => {
console.log('Currently session info: ', session);
});
```
If you're using CouchDB to authenticate users in your application, you could call `session` when your app is initialized in order to determine if a user is logged in, and if so, any other information stored in the `_users` document.
### Dealing with Documents
Whether authentication is used or not, a document is always returned to you by RxCouch in the form a `BehaviorSubject` which provides a real-time two-way data binding with the document.
#### Subscribe to Any Document in Real Time
```typescript
interface Person {
name: string;
email?: string;
phone?: string;
}
const myDocument: BehaviorSubject = couchDbConnection.doc('778...05b');
myDocument.subscribe((document: Person) => {
console.log('Most recent person: ', document);
});
//...
```
###### Result
```typescript
Most recent person: { _id: "7782f0743bee05005a548ba8af00205b", _rev: "10-bcaab49ec87c678686984d1c4873cd3e", name: 'Mike" }
```
#### Update The Same Document in Real Time
```typescript
//...
const myCompletelyChangedDocument = {
name: 'some new name here',
email: 'mwk@mikekeen.com',
phone: '323-209-5336'
}
myDocument.next(myCompletelyChangedDocument);
```
###### All Connected Clients Result:
```typescript
Most recent person: { _id: "7782f0743bee05005a548ba8af00205b", _rev: "11-bf3003bb5f63b875db4284f319a0b918", name: "some new name here", email: "mwk@mikekeen.com", phone: "323-209-5336"}
```
### Creating And Modifying Documents
In the above example, a document was fetched by its `_id`. The `doc()` function alternatively accepts an object as an argument. If an object is passed in, one of two things will happen:
1. If the object contains both an `_id` and `_rev` field, the rest of the fields in the object will be used to update the document that matches the `_id`.
2. If the object does not contain both an `_id` and a `_rev` field, then a new document will be created based on the fields contained in the object passed.
Whichever of the above happens, `doc` returns a `BehaviorSubject` that will reflect all future changes to the relevant document, and pass all changes upstream when `.next()` is called.
#### Create a New Document and Subscribe to Future Changes
```typescript
//...
const newlyCreatedPerson = couchDbConnection.doc({
name: 'tracy',
email: 'tracy@company.com',
phone: '323-209-5336'
}).subscribe((doc: Person) => {
console.log('Person Feed: ', doc);
});
```
###### Result
```typescript
Person Feed: {"_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "1-2fee21d51258845545ea6506ab138919", "name": "tracy", "email": "tracy@company.com", "phone": "323-209-5336"}
```
#### Modify an Existing Document and Subscribe to Future Changes
There are two ways to modify a document that already exists in CouchDB.
One way, is to simply pass a modified version of the existing document to `doc` like in the example below:
```typescript
//...
couchDbConnection.doc({
_id: '7782f0743bee05005a548ba8af00b4f5',
_rev: '1-2fee21d51258845545ea6506ab138919',
name: 'tracy',
email: 'tracy@company-modified.com',
phone: '323-209-5336'
}).subscribe((doc) => {
console.log('Document Modified: ', doc);
})
```
###### Result
```typescript
Document Modified: { "_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "2-e654adf15f28b99f26ae1f0dbe8e7c36", "name": "tracy", "email": "tracy@company-modified.com", "phone": "323-209-5336" }
Person Feed: {"_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "2-e654adf15f28b99f26ae1f0dbe8e7c36", "name": "tracy", "email": "tracy@company-modified.com", "phone": "323-209-5336" }
```
Another way to modify a document that already exists in CouchDB is to just call `next` on an already fetched document's `BehaviorSubject`.
```typescript
//..
newlyCreatedPerson.next({
name: 'tracy Modified!',
email: 'tracy@company-modified-again.com',
phone: '323-209-5336'
});
```
###### Result
```typescript
Person Feed: {"_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "3-8da970f593132c80ccee83fc4708ce33", "name": "tracy Modified!", "email": "tracy@company-modified-again.com", "phone": "323-209-5336" }
```
### Finding Documents With a Query
RxCouch exposes `find` from `CouchDB` that uses [CouchDB Selector Syntax](https://docs.couchdb.org/en/2.2.0/api/database/find.html#selector-syntax) to make queries, and returns a list of matching documents. Warning: Documents returned are not `BehaviorSubject`s!
### Get a List of Documents That Match a Query
Call `find` with `(CouchDBFindQuery)`.
An `CouchDBFindQuery`, when passed into `find`, should conform to [CouchDB Selector Syntax](https://docs.couchdb.org/en/2.2.0/api/database/find.html#selector-syntax). It can have the following optional properties of the following types:
`selector: CouchDBFindSelector`
`limit: number`
`skip: number`
`sort: CouchDBFindSort[]`
`fields: string[]`
`use_index: string | []`
`r: number`
`bookmark: string`
`update: boolean`
`stable: boolean`
`stale: string`
`execution_stats: boolean`
A call to `find` will return an `Observable` that will emit a list of documents that match the passed query.
```typescript
//...
couchDbConnection.find({
selector: {
name: 'tracy'
}
}).subscribe((matchingPeople: Person[]) => {
console.log('Matching people: ', matchingPeople);
});
```
###### Result
```typescript
Matching people: [{ "_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "3-8da970f593132c80ccee83fc4708ce33", "name": "tracy Modified!", "email": "tracy@company-modified-again.com", "phone": "323-209-5336" }]
```
#### Get Real-Time Feeds for Found Documents
```typescript
//...
couchDbConnection.find({
selector: {
name: 'some new name here'
}
}).subscribe((matchingPeople: Person[]) => {
const documentFeeds = matchingPeople.map((person: Person) => {
return couchDbConnection.doc(matchingPeople)
});
documentFeeds.forEach((feed) => {
feed.subscribe(
(document: Person) => console.log('Latest person: ', document)
);
});
});
```
###### Result
```typescript
Latest person: { "_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "3-8da970f593132c80ccee83fc4708ce33", "name": "tracy Modified!", "email": "tracy@company-modified-again.com", "phone": "323-209-5336" }
```
## π Under the Hood
#### Document Tracking
Documents are automatically cached and then tracked for changes. `CouchDBDocumentCollection` handles both caching and change tracking.
##### Cache
Instances of `CouchDB` have a method called `doc`. Any documents that flow through `doc` are cached in a `CouchDBDocumentCollection`. They can later be retrieved by _id in the form of a `BehaviorSubject` that supports two way binding. A hash of the document is indexed (by document id) for change tracking purposes.
##### Change Tracking
Before document changes are propagated either to or from a `BehaviorSubject`, the potentially changed document is hashed and compared against a previously indexed hash of the document. If the hashes don't match, the document has changed, and it will be passed into the `next` method of the relavent indexed `BehaviorSubject`. Finally, the indexed hash entry for the document will be updated.
## Thank you for using RxCouch!
πΊπΈ