UNPKG

@platform/cell.client

Version:

A strongly typed HTTP client for operating with a CellOS service end-point.

109 lines (108 loc) 4.2 kB
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; }