@platform/cell.client
Version:
A strongly typed HTTP client for operating with a CellOS service end-point.
109 lines (108 loc) • 4.2 kB
JavaScript
import { Subject } from 'rxjs';
import { debounceTime, filter, map, share, takeUntil } from 'rxjs/operators';
import { coord, defaultValue, ERROR, MemoryQueue } from '../common';
export function saveMonitor(args) {
const { client } = args;
const http = client.http;
const debounce = defaultValue(args.debounce, 300);
const queue = MemoryQueue.create();
const dispose$ = new Subject();
const changed$ = client.changes.changed$.pipe(takeUntil(dispose$));
const subject$ = args.event$ || new Subject();
const event$ = subject$.pipe(takeUntil(dispose$), share());
const fire = (e) => subject$.next(e);
let pending = {};
changed$.pipe(debounceTime(debounce)).subscribe((e) => api.save());
changed$.subscribe((e) => {
const { sheet } = e;
const ns = sheet.uri.toString();
let changes = pending[ns] || {};
Object.keys(e.changes).forEach((key) => {
changes = Object.assign(Object.assign({}, changes), { [key]: Object.assign(Object.assign({}, changes[key]), e.changes[key]) });
});
pending[ns] = changes;
});
const save = async (changes) => {
const target = client.http.origin;
const sheets = client.changes.watching;
const findSheet = (ns) => sheets.find((sheet) => sheet.uri.toString() === ns);
const changeSet = Object.keys(changes)
.map((ns) => ({
ns,
changes: changes[ns],
sheet: findSheet(ns),
}))
.filter((sheet) => Boolean(sheet));
changeSet.forEach(({ sheet, changes }) => {
fire({ type: 'SHEET/saving', payload: { target, sheet, changes } });
});
const wait = changeSet.map(({ ns, changes }) => saveSheet({ ns, http, changes }));
const res = await Promise.all(wait);
res
.filter(({ ok }) => ok)
.map(({ ns }) => findSheet(ns))
.filter((sheet) => Boolean(sheet))
.forEach((sheet) => sheet.state.clear.changes('SAVE'));
changeSet.forEach(({ sheet, changes }) => {
const ns = sheet.uri.toString();
const errors = res
.filter((res) => res.ns === ns)
.filter((res) => Boolean(res.error))
.map((res) => ({ ns, error: res.error }));
const ok = errors.length === 0;
fire({ type: 'SHEET/saved', payload: { ok, target, sheet, changes, errors } });
fire({ type: 'SHEET/sync', payload: { ns, changes } });
});
};
const api = {
event$,
saving$: event$.pipe(filter((e) => e.type === 'SHEET/saving'), map((e) => e.payload), share()),
saved$: event$.pipe(filter((e) => e.type === 'SHEET/saved'), map((e) => e.payload), share()),
get debounce() {
return debounce;
},
get isDisposed() {
return !dispose$.isStopped;
},
dispose() {
dispose$.next();
dispose$.complete();
},
async save() {
const changes = Object.assign({}, pending);
pending = {};
return queue.push(() => save(changes));
},
};
return api;
}
async function saveSheet(args) {
const { ns, http, changes } = args;
const client = http.ns(ns);
let error;
if (changes.cells || changes.ns) {
const payload = {
cells: changes.cells ? toChangedCells(changes) : undefined,
ns: changes.ns ? changes.ns.to : undefined,
};
const res = await client.write(payload, { data: false });
if (!res.ok) {
error = {
status: res.status,
type: ERROR.HTTP.SERVER,
message: `Failed while saving data to ${http.origin}`,
children: res.error ? [res.error] : undefined,
};
}
}
const ok = !Boolean(error);
return { ok, ns, error };
}
function toChangedCells(changes) {
const res = {};
const cells = changes.cells || {};
Object.keys(cells)
.filter((key) => coord.cell.isCell(key))
.forEach((key) => (res[key] = cells[key].to));
return res;
}