src/util.js
/**
* utilities, many of which operate against rethinkdb independent of
* Supermodel constructs.
*/
// @flow
import uuid from 'uuid';
import * as nrser from 'nrser';
const _ = nrser._;
const Promise = nrser.Promise;
// types
import type {
IRethinkDB,
IConnection,
} from './types';
/**
* attempt to connect to the database, re-scheduling an attempt with a back-off
* timeout if it fails.
*
* wraps this all up in a Promise that resolved when the connection succeeds
* or rejects when it runs out of attempts.
*/
function attemptToConnect(
r: IRethinkDb,
{
resolve,
reject,
host,
port,
db,
attemptNumber,
maxAttempts,
baseTimeout,
maxTimeout,
}: {
resolve: Function,
reject: Function,
host: string,
port: number,
db: string,
attemptNumber: number,
maxAttempts: number,
baseTimeout: number,
maxTimeout: number,
}
): Promise<IConnection> {
info: `attempt ${ attemptNumber } to connect to db`;
r.connect(
{host, db, port},
(error: ?Error, conn: ?IConnection): void => {
if (error) {
error: `failed to connect to db on attempt ${ attemptNumber }`,
{error};
if (maxAttempts > 0 && attemptNumber >= maxAttempts) {
error: "out of attempts, rejecting."
reject(error);
} else {
const timeout = Math.min(timeout * attemptNumber, maxTimeout);
info: `scheduling connection attempt in ${ timeout }ms`;
setTimeout(
() => {
attemptToConnect({
resolve,
reject,
attemptNumber: attemptNumber + 1,
maxAttempts,
timeout,
})
},
timeout,
);
}
} else {
info: `db connected on attempt ${ attemptNumber }`;
resolve(conn);
}
}
);
} // .attemptToConnect
/**
* connect to the db, using `.attemptToConnect` internally.
*
* by default will continue to try connecting every `timeout` milliseconds
* (default 1000ms = 1s).
*/
export function connect(
r: IRethinkDb,
{
host,
port = 28015,
db,
maxAttempts = 0,
baseTimeout = 1000,
maxTimeout = 5000,
}: {
host: string,
port?: number,
db?: string,
maxAttempts?: number,
baseTimeout?: number,
maxTimeout?: number,
} = {}
): Promise<IConnection> {
return new Promise((resolve, reject) => {
attemptToConnect(r, {
resolve,
reject,
host,
port,
db,
attemptNumber: 1,
maxAttempts,
baseTimeout,
maxTimeout,
});
});
} // .connect
/**
* makes sure a db exists by attempting to create it and suppressing the
* error if it already exists.
*
* @return [Promise<boolean>] true if the db was created.
*/
export function ensureDB(
r: IRethinkDB,
conn: IConnection,
dbName: string,
): Promise<boolean> {
debug: `ensuring db ${ dbName }...`;
return r.dbCreate(dbName).run(conn)
.then(() => {
debug: `db ${ dbName } created.`;
return true;
})
.catch((error: Error) => {
if (error.message.match(/Database\ .*\ already\ exists/)) {
debug: `db ${ dbName } already present.`;
return false;
}
throw error;
});
} // .ensureDB
/**
* makes sure a table exists by attempting to create it and suppressing the
* error if it already exists.
*
* @return [Promise<boolean>] true if the table was created.
*/
export function ensureTable(
r: RethinkDB,
conn: IConnection,
tableName: string,
): Promise<boolean> {
return r.tableCreate(tableName).run(conn)
.then(() => true)
.catch((error: Error) => {
if (error.message.match(/Table\ .*\ already\ exists/)) {
return false;
}
throw error;
});
} // .ensureTable
/**
* ensure that an index exists on a table, creating if necessary.
*
* returns `Promise<boolean>` that resolved `true` if the index was created
* and `false` if it was already there.
*/
export function ensureIndex(
r: IRethinkDB,
conn: IConnection,
tableName: string,
indexName: string,
...indexCreateOptions: Array<*>
): Promise<boolean> {
debug: `ENSURING index ${ indexName } on table ${ tableName }`, {
indexCreateOptions
};
return r
.table(tableName)
.indexCreate(indexName, ...indexCreateOptions)
.run(conn)
.then((): boolean => {
debug: `ensuring index ${ indexName } CREATED on table ${ tableName }`;
return true;
})
.catch((error: Error) => {
if (error.message.match(/Index\ .*\ already\ exists/)) {
debug: `ensuring index ${ indexName } EXISTED on table ${ tableName }`;
return false;
}
throw error;
});
} // .ensureIndex