@graffy/server
Version:
Node.js library for building an API for a Graffy store.
74 lines (68 loc) • 2.24 kB
JavaScript
import url from 'url';
import { decodeUrl } from '@graffy/common';
export default function server(store) {
if (!store) throw new Error('server.store_undef');
return async (req, res) => {
const parsed = url.parse(req.url, true);
const query = parsed.query.q && decodeUrl(parsed.query.q);
const options =
parsed.query.opts && JSON.parse(decodeURIComponent(parsed.query.opts));
if (req.method === 'GET') {
try {
if (req.headers['accept'] === 'text/event-stream') {
res.setHeader('content-type', 'text/event-stream');
const keepAlive = setInterval(() => {
if (req.aborted || res.finished) {
clearInterval(keepAlive);
return;
}
res.write(': \n\n');
}, 29000);
// TODO: Resumable subscriptions using timestamp ID.
// const lastId = req.headers['last-event-id'];
try {
const stream = store.call('watch', query, {
...options,
raw: true,
});
for await (const value of stream) {
if (req.aborted || res.finished) break;
res.write(`data: ${JSON.stringify(value)}\n\n`);
}
} catch (e) {
res.write(`event: graffyerror\ndata: ${e.message}\n\n`);
}
res.end();
} else {
const value = await store.call('read', query, {
...options,
raw: true,
});
res.writeHead(200);
res.end(JSON.stringify(value));
}
} catch (e) {
res.writeHead(400);
res.end(`${e.message}\n\n`);
return;
}
} else if (req.method === 'POST') {
try {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const change = JSON.parse(Buffer.concat(chunks).toString());
const value = await store.call('write', change, options);
res.writeHead(200);
res.end(JSON.stringify(value));
} catch (e) {
res.writeHead(400);
res.end(`${e.message}\n\n`);
return;
}
} else {
res.writeHead(501);
res.end('Not implemented');
}
};
}
// TODO: Write tests!