@cloud-cli/store
Version:
Data store abstraction
114 lines (113 loc) • 3.99 kB
JavaScript
import { Resource, ResourceDriver } from './resource.js';
import { randomUUID } from 'crypto';
const headers = { 'content-type': 'application/json' };
export class StoreDriver extends ResourceDriver {
constructor(baseUrl = process.env.STORE_URL) {
super();
this.storeUrl = String(baseUrl).replace(/\/$/, '');
}
async create(resource) {
const desc = Resource.describe(resource);
const { name } = desc;
const url = this.storeUrl + '/' + name + '/0';
try {
await StoreDriver.fetch(url, {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: '{}',
});
await StoreDriver.fetch(url, { method: 'DELETE' });
}
catch {
throw new Error(`Cannot create resource "${name}"`);
}
}
async save(model) {
const desc = Resource.describe(model);
const { url, id } = this.getUrl(model);
desc.fields.forEach((field) => {
model[field.name] = model[field.name] ?? field.defaultValue;
});
const body = JSON.stringify(model);
const remote = await StoreDriver.fetch(url, { method: 'PUT', headers, body });
if (remote.ok) {
return id;
}
throw new Error(String(remote.status));
}
async remove(model) {
const { url } = this.getUrl(model);
return void (await StoreDriver.fetch(url, { method: 'DELETE' }));
}
async find(model) {
const { url } = this.getUrl(model);
const resource = Object.getPrototypeOf(model).constructor;
const remote = await StoreDriver.fetch(url);
if (remote.ok) {
return this.createModel(resource, await remote.json());
}
throw new Error('Not found');
}
async findAll(resource, query) {
const desc = Resource.describe(resource);
const { name } = desc;
const url = this.storeUrl + '/' + name;
const remote = await StoreDriver.fetch(url);
if (remote.ok) {
const map = await remote.json();
const items = Object.values(map).map((raw) => this.createModel(resource, raw));
return this.filter(items, query);
}
throw new Error('Not found');
}
getUrl(model) {
const desc = Resource.describe(model);
const { name } = desc;
const primary = desc.fields.find((field) => field.primary);
if (!model[primary.name]) {
model[primary.name] = StoreDriver.uid();
}
const id = model[primary.name];
const url = this.storeUrl + '/' + name + '/' + id;
return { url, id };
}
filter(items, query) {
const filters = query.toJSON();
let next;
while (items.length && (next = filters.shift())) {
const [left, op, right] = next;
items = items.filter((item) => this.compare(item, left, op, right));
}
return items;
}
compare(item, left, op, right) {
switch (op) {
case '=':
return item[left] == right;
case '!=':
return item[left] != right;
case 'like':
return String(item[left]).includes(String(right));
case '>':
return item[left] > right;
case '<':
return item[left] < right;
case '>=':
return item[left] >= right;
case '<=':
return item[left] <= right;
default:
return true;
}
}
createModel(model, data) {
const desc = Resource.describe(model);
const modelData = {};
desc.fields.forEach((field) => {
modelData[field.name] = data[field.name] ?? field.defaultValue;
});
return new model(modelData);
}
}
StoreDriver.fetch = globalThis.fetch;
StoreDriver.uid = randomUUID;