Created
September 25, 2016 16:38
-
-
Save djfm/3c87696e3f94c691e88af513d6205fec to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
Cluster, | |
N1qlQuery, | |
MutationState, | |
} = require('couchbase'); | |
const URL = require('url'); | |
const addQueryParams = params => url => { | |
const parsed = URL.parse(url); | |
return URL.format(Object.assign({}, parsed, { query: Object.assign( | |
{}, parsed.query, params | |
) })); | |
}; | |
const makeOrConditions = (key, predicates, paramIndex) => | |
predicates.reduce( | |
({ orConditions, params }, value, i) => ({ | |
orConditions: orConditions.concat(`\`${key}\`=$${paramIndex + i}`), | |
params: params.concat(value), | |
}), | |
{ orConditions: [], params: [] } | |
) | |
; | |
const makeAndConditions = keyConditions => | |
Object.keys(keyConditions).reduce( | |
({ conditions, params }, key) => { | |
const { | |
orConditions, | |
params: newParams, | |
} = makeOrConditions( | |
key, | |
[].concat(keyConditions[key]), // "return" in the list Monad | |
params.length + 1 | |
); | |
return { | |
conditions: conditions.concat(`(${orConditions.join(' OR ')})`), | |
params: params.concat(newParams), | |
}; | |
}, | |
{ conditions: [], params: [] } | |
); | |
const makeWhere = keyConditions => { | |
const keys = Object.keys(keyConditions); | |
if (keys.length === 0) { | |
return { | |
keys, | |
params: [], | |
where: undefined, | |
}; | |
} | |
const { | |
conditions, | |
params, | |
} = makeAndConditions(keyConditions); | |
return { | |
keys, | |
params, | |
where: conditions.join(' AND '), | |
}; | |
}; | |
const makeOrderBy = ({ orderBy, sortOrder }) => ( | |
orderBy ? `${orderBy} ${sortOrder}` : undefined | |
); | |
const makeLimit = ({ page, resultsPerPage }) => ( | |
page ? | |
`${resultsPerPage} OFFSET ${(page - 1) * resultsPerPage}` : | |
undefined | |
); | |
const pairIfSecond = (first, second) => ( | |
second ? [first, second] : [] | |
); | |
const buildStatement = ({ | |
action, | |
fromClause, | |
where, | |
orderBy, | |
limit, | |
}) => [ | |
action, | |
'FROM', fromClause, | |
...pairIfSecond('WHERE', where), | |
...pairIfSecond('ORDER BY', orderBy), | |
...pairIfSecond('LIMIT', limit), | |
].join(' '); | |
const canonicalizeSortOrder = sortOrder => ( | |
(sortOrder || 'asc').toLowerCase() === 'asc' ? | |
'ASC' : | |
'DESC' | |
); | |
const createAdapter = (bucketName, bucket, consistently) => { | |
const get = (key, options = {}) => new Promise( | |
(resolve, reject) => { | |
bucket.get(key, options, (err, data) => { | |
if (err) { | |
reject(err); | |
} else { | |
resolve(Object.assign({}, data, { key })); | |
} | |
}); | |
} | |
); | |
const insert = (key, value, options = {}) => new Promise( | |
(resolve, reject) => { | |
const valueWithId = Object.assign({}, value, { | |
id: key, | |
}); | |
bucket.insert(key, valueWithId, options, (err, data) => { | |
if (consistently instanceof MutationState) { | |
consistently.add(data.token); | |
} | |
if (err) { | |
reject(err); | |
} else { | |
resolve(Object.assign({}, data, { key, value })); | |
} | |
}); | |
} | |
); | |
const upsert = (key, value, options = {}) => new Promise( | |
(resolve, reject) => { | |
bucket.upsert(key, value, options, (err, data) => { | |
if (consistently instanceof MutationState) { | |
consistently.add(data.token); | |
} | |
if (err) { | |
reject(err); | |
} else { | |
resolve(Object.assign({}, { key, value })); | |
} | |
}); | |
} | |
); | |
const remove = (key, options = {}) => new Promise( | |
(resolve, reject) => { | |
bucket.remove(key, options, (err, data) => { | |
if (consistently instanceof MutationState) { | |
consistently.add(data.token); | |
} | |
if (err) { | |
reject(err); | |
} else { | |
resolve({ key }); | |
} | |
}); | |
} | |
); | |
const dumbQuery = (query, params = []) => new Promise( | |
(resolve, reject) => { | |
bucket.query(query, params, (err, rows) => { | |
if (err) { | |
reject(err); | |
} else { | |
resolve(rows); | |
} | |
}); | |
} | |
); | |
const query = (str, params = []) => { | |
const q = N1qlQuery.fromString(str); | |
if (consistently instanceof MutationState) { | |
q.consistentWith(consistently); | |
} else if (consistently) { | |
q.consistency(N1qlQuery.Consistency.REQUEST_PLUS); | |
} | |
return dumbQuery(q, params); | |
}; | |
const removeAll = (keyConditions = {}) => { | |
const { params, where } = makeWhere(keyConditions); | |
return query( | |
buildStatement({ | |
action: 'DELETE', | |
fromClause: `\`${bucketName}\``, | |
where, | |
}), | |
params | |
); | |
}; | |
const getAll = (keyConditions = {}, pagination = {}) => { | |
const { params, where } = makeWhere(keyConditions); | |
const { | |
page, | |
resultsPerPage, | |
orderBy, | |
sortOrder, | |
} = Object.assign({ | |
resultsPerPage: 12, | |
}, pagination, { | |
sortOrder: canonicalizeSortOrder(pagination.sortOrder), | |
}); | |
return query( | |
buildStatement({ | |
action: 'SELECT r.*', | |
fromClause: `\`${bucketName}\` r`, | |
where, | |
orderBy: makeOrderBy({ orderBy, sortOrder }), | |
limit: makeLimit({ page, resultsPerPage }), | |
}), | |
params | |
); | |
}; | |
const count = keyConditions => { | |
const { params, where } = makeWhere(keyConditions); | |
return query( | |
buildStatement({ | |
action: 'SELECT COUNT(*) n', | |
fromClause: `\`${bucketName}\` r`, | |
where, | |
}), | |
params | |
).then(([{ n }]) => n); | |
}; | |
const makeId = key => new Promise( | |
(resolve, reject) => bucket.counter( | |
key, 1, { initial: 0 }, (err, data) => { | |
if (err) { | |
reject(err); | |
} else { | |
resolve(data.value); | |
} | |
} | |
) | |
); | |
return { | |
insert, | |
upsert, | |
remove, | |
get, | |
count, | |
removeAll, | |
getAll, | |
makeId, | |
}; | |
}; | |
const createDb = ({ clusterURI, bucketName }) => { | |
const cluster = new Cluster( | |
addQueryParams({ fetch_mutation_tokens: 'true' })(clusterURI) | |
); | |
const bucket = cluster.openBucket(bucketName); | |
const adapter = createAdapter(bucketName, bucket, false); | |
const consistentAdapter = createAdapter(bucketName, bucket, true); | |
return Object.assign(adapter, { | |
consistently: consistentAdapter, | |
contextAware: () => createAdapter(bucketName, bucket, new MutationState()), | |
}); | |
}; | |
module.exports = createDb; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment