Skip to content
/ rxcouch Public

πŸ›‹ (Universal) CouchDB Client Powered by ReactiveX (RxJS)

License

Notifications You must be signed in to change notification settings

mkeen/rxcouch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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

import { CouchDB } from '@mkeen/rxcouch';

Initialize RxCouch for Connecting to a CouchDB Database

const couchDbConnection = new CouchDB(
  {
    dbName: 'people',
  }
);

Basic Configuration Options

CouchDB is initialized with: (RxCouchConfig, AuthorizationBehavior?, Observable<CouchDBCredentials>?)

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

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.

//...
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

interface Person {
  name: string;
  email?: string;
  phone?: string;
}

const myDocument: BehaviorSubject<Person> = couchDbConnection.doc('778...05b');

myDocument.subscribe((document: Person) => {
  console.log('Most recent person: ', document);
});

//...
Result
Most recent person: { _id: "7782f0743bee05005a548ba8af00205b", _rev: "10-bcaab49ec87c678686984d1c4873cd3e", name: 'Mike" }

Update The Same Document in Real Time

//...
const myCompletelyChangedDocument = {
  name: 'some new name here',
  email: 'mwk@mikekeen.com',
  phone: '323-209-5336'
}

myDocument.next(myCompletelyChangedDocument);
All Connected Clients Result:
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

//...
const newlyCreatedPerson = couchDbConnection.doc({
  name: 'tracy',
  email: 'tracy@company.com',
  phone: '323-209-5336'
}).subscribe((doc: Person) => {
  console.log('Person Feed: ', doc);
});
Result
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:

//...
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
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.

//..
newlyCreatedPerson.next({
  name: 'tracy Modified!',
  email: 'tracy@company-modified-again.com',
  phone: '323-209-5336'
});
Result
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 to make queries, and returns a list of matching documents. Warning: Documents returned are not BehaviorSubjects!

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

//...
couchDbConnection.find({
  selector: {
    name: 'tracy'
  }
  
}).subscribe((matchingPeople: Person[]) => {
  console.log('Matching people: ', matchingPeople);
});
Result
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

//...
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
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!

πŸ‡ΊπŸ‡Έ