<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: index.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: index.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>import 'regenerator-runtime/runtime'; /** Class implementing the Apollo Passport DBDriver interface */ class RethinkDBDashDriver { /** * Returns a DBDriver instance (for use by Apollo Passport). Parameters are * driver-specific and should be clearly specificied in the README. * This documents the RethinkDBDash DBDriver specifically, although some * *options* are relevant for all drivers. * * @param {rethinkdbdash} r, e.g. var r = require('rethinkdbdash')(); * * @param {string} options.userTableName default: 'users' * @param {string} options.configTableName default: 'apolloPassportConfig' * @param {string} options.dbName default: current database */ constructor(r, options = {}) { this.r = r; this.userTableName = options.userTableName || 'users'; this.configTableName = options.configTableName || 'apolloPassportConfig'; this.dbName = options.dbName || (r._poolMaster && r._poolMaster._options.db); this.db = r.db(this.dbName); this.readySubs = []; // don't await the init, run async if (options.init !== false) this._init(); } /** * Internal method, documented for benefit of driver authors. Most important * is to call fetchConfig() (XXX unfinished), but may also assert that all * tables exist, and run ready callbacks. */ async _init() { await this._assertTableExists(this.userTableName); this.users = this.db.table(this.userTableName); await this._assertTableExists(this.configTableName); this.config = this.db.table(this.configTableName); this.initted = true; while(this.readySubs.length) this.readySubs.shift().call(); } /** * Internal method, documented for benefit of driver authors. An awaitable * promise that returns if the driver is ready (or when it becomes ready). */ _ready() { return new Promise((resolve) => { if (this.initted) resolve(); else this.readySubs.push(resolve); }); } ////////////////// // DB UTILITIES // ////////////////// /** * Internal method, documented for benefit of driver authors. Asserts (and * awaits) that the given table name exists. This is a convenience for the * user but with RethinkDB **there is no safe way to do this** other than * creating the table in advance (outside of the app). It's fine if the * table is created with only one app instance running, which is usually * the case for initial setup. * * @param {string} name - the name of the table to assert */ async _assertTableExists(name) { try { await this.db.tableCreate(name).run(); } catch (err) { if (err.msg !== `Table \`${this.dbName}.${name}\` already exists.`) { throw err; } // XXX give a warning about the caveat in the docs above. } } ////////////////// // CONFIG TABLE // ////////////////// /** * Retrieves _all_ configuration from the database. * @return {object} A nested dictionary arranged by type, i.e. * * ```js * { * service: { // type * facebook: { // id * ...data // value (de-JSONified if from non-document DB) * } * } * } * ``` */ async fetchConfig() { await this._ready(); const results = await this.config.run(); const out = {}; results.forEach(row => { if (!out[row.type]) out[row.type] = {}; out[row.type][row.id] = row; }); return out; } /** * Creates or updates the key with the given value. * NoSQL databases can store the destructured value as part of the record. * Fixed-schema databases should JSON-encode the 'value' column. * * @param {string} type - e.g. "service" * @param {string} id - e.g. "facebook" * @param {object} value - e.g. { id: 1, ...profile } */ async setConfigKey(type, id, value) { await this._ready(); await this.config.insert({ type, id, ...value }); } /////////// // USERS // /////////// /** * Given a user record, save it to the database, and return its given id. * NoSQL databases should store the entire object, schema-based databases * should honor the 'emails' and 'services' keys and store as necessary * in another table. * * @param {object} user * * { * emails: [ { address: "me@me.com" } ], * services: [ { facebook: { id: 1, ...profile } } ] * ...anyOtherDataForUserRecordAtCreationTimeFromAppHooks * } * * @return {string} the id of the inserted user record */ async createUser(user) { await this._ready(); let id = user.id; const result = await this.users.insert(user); if (!id) id = result.generated_keys[0]; return id; } /** * Fetches a user record by id. Schema-based databases should merge * appropriate user-data from e.g. `user_emails` and `user_services`. * * @param {string} id - the user record's id * * @return {object} user object in the same format expected by * {@link RethinkDBDashDriver#createUser}, or *null* if none found. */ async fetchUserById(userId) { await this._ready(); return this.users.get(userId).run(); } /** * Given a single "email" param, returns the matching user record if one * exists, or null, otherwise. * * @param {string} email - the email address to search for, e.g. "me@me.com" * * @return {object} user object in the same format expected by * {@link RethinkDBDashDriver#createUser}, or *null* if none found. */ async fetchUserByEmail(email) { await this._ready(); const results = await this.users .filter(this.r.row('emails').contains(row => row('address').eq(email))) .limit(1) .run(); return results[0] || null; } /** * Returns a user who has *either* a matching email address or matching * service record, or null, otherwise. * * @param {string} service - name of the service, e.g. "facebook" * @param {string} id - id of the service record, e.g. "152356242" * @param {string} email - the email address to search for, e.g. "me@me.com" * * @return {object} user object in the same format expected by * {@link RethinkDBDashDriver#createUser}, or *null* if none found */ async fetchUserByServiceIdOrEmail(service, id, email) { await this._ready(); const results = await this.users.filter( this.r.or( this.r.row('services')(service)('id').eq(id).default(false), this.r.row('emails').contains({ address: email }).default(false) ) ).limit(1).run(); return results[0] || null; } /** * Given a userId, ensures the user record contains the given email * address, and updates it with optional data. * * @param {string} userId - the id of the user to assert * @param {string} email - the email address to ensure exists * @param {object} data - optional, e.g. { type: 'work', verified: true } */ async assertUserEmailData(userId, email, data) { await this._ready(); await this.users.get(userId).update(row => ({ emails: row('emails').default([]) .filter(row('emails').default([]).contains({ address: email }).not()) .append({ address: email, ...data }) })); } /** * Given a userId, ensure the user record contains the given service * record, and updates it with the given data. * * @param {string} userId - the id of the user to assert * @param {string} service - the name of the service, e.g. "facebook" * @param {object} data - e.g. { id: "4321", displayName: "John Sheppard" } */ async assertUserServiceData(userId, service, data) { await this._ready(); await this.users.get(userId).update({ services: { [service]: data } }); } // Not sure if we need this anymore, since fetch*() functions return // normalized data. But let's see. mapUserToServiceData(user, service) { return user && user.services && user.services[service]; } } export default RethinkDBDashDriver; </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="RethinkDBDashDriver.html">RethinkDBDashDriver</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Sat Aug 20 2016 13:51:25 GMT+0300 (IDT) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>