π 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.
RxJS 6+
CouchDB 2.3+, CouchDB 3.0+
NPM: npm install @mkeen/rxcouch
Yarn: yarn install @mkeen/rxcouch
import { CouchDB } from '@mkeen/rxcouch';
const couchDbConnection = new CouchDB(
{
dbName: 'people',
}
);
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' }
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.
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.
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.
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);
});
//...
Most recent person: { _id: "7782f0743bee05005a548ba8af00205b", _rev: "10-bcaab49ec87c678686984d1c4873cd3e", name: 'Mike" }
//...
const myCompletelyChangedDocument = {
name: 'some new name here',
email: 'mwk@mikekeen.com',
phone: '323-209-5336'
}
myDocument.next(myCompletelyChangedDocument);
Most recent person: { _id: "7782f0743bee05005a548ba8af00205b", _rev: "11-bf3003bb5f63b875db4284f319a0b918", name: "some new name here", email: "mwk@mikekeen.com", phone: "323-209-5336"}
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:
- 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
. - 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.
//...
const newlyCreatedPerson = couchDbConnection.doc({
name: 'tracy',
email: 'tracy@company.com',
phone: '323-209-5336'
}).subscribe((doc: Person) => {
console.log('Person Feed: ', doc);
});
Person Feed: {"_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "1-2fee21d51258845545ea6506ab138919", "name": "tracy", "email": "tracy@company.com", "phone": "323-209-5336"}
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);
})
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'
});
Person Feed: {"_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "3-8da970f593132c80ccee83fc4708ce33", "name": "tracy Modified!", "email": "tracy@company-modified-again.com", "phone": "323-209-5336" }
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 BehaviorSubject
s!
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);
});
Matching people: [{ "_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "3-8da970f593132c80ccee83fc4708ce33", "name": "tracy Modified!", "email": "tracy@company-modified-again.com", "phone": "323-209-5336" }]
//...
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)
);
});
});
Latest person: { "_id": "7782f0743bee05005a548ba8af00b4f5", "_rev": "3-8da970f593132c80ccee83fc4708ce33", "name": "tracy Modified!", "email": "tracy@company-modified-again.com", "phone": "323-209-5336" }
Documents are automatically cached and then tracked for changes. CouchDBDocumentCollection
handles both caching and change tracking.
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.
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.
πΊπΈ