Skip to content

Instantly share code, notes, and snippets.

@djfm
Created September 25, 2016 16:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djfm/3c87696e3f94c691e88af513d6205fec to your computer and use it in GitHub Desktop.
Save djfm/3c87696e3f94c691e88af513d6205fec to your computer and use it in GitHub Desktop.
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