UNPKG

orange-orm

Version:

Object Relational Mapper

143 lines (122 loc) 3.7 kB
const fastjson = require('fast-json-patch'); let fromCompareObject = require('./fromCompareObject'); let toCompareObject = require('./toCompareObject'); let getSessionSingleton = require('./table/getSessionSingleton'); function applyPatch({ options = {}, context }, dto, changes, column) { let dtoCompare = toCompareObject(dto); changes = validateReadonly(dtoCompare, changes); if (column.tsType === 'JSONColumn') { const engine = context ? getSessionSingleton(context, 'engine') : undefined; if(column && engine === 'sap') { changes = validateConflict(dtoCompare, changes); fastjson.applyPatch(dtoCompare, changes, true, true); } } else fastjson.applyPatch(dtoCompare, changes, true, true); let result = fromCompareObject(dtoCompare); if (Array.isArray(dto)) dto.length = 0; else for (let name in dto) { delete dto[name]; } for (let name in result) { dto[name] = result[name]; } return dto; function validateReadonly(object, changes) { return changes.filter(change => { const option = getOption(change.path); let readonly = option.readonly; if (readonly) { const e = new Error(`Cannot update column ${change.path.replace('/', '')} because it is readonly`); // @ts-ignore e.status = 405; throw e; } return true; }); } function getOption(path) { let splitPath = path.split('/'); splitPath.shift(); return splitPath.reduce(extract, options); function extract(obj, name) { if (Array.isArray(obj)) return obj[0] || options; if (obj === Object(obj)) return obj[name] || options; return obj; } } function validateConflict(object, changes) { return changes.filter(change => { let expectedOldValue = change.oldValue; const option = getOption(change.path); let readonly = option.readonly; if (readonly) { const e = new Error(`Cannot update column ${change.path.replace('/', '')} because it is readonly`); // @ts-ignore e.status = 405; throw e; } let concurrency = option.concurrency || 'optimistic'; if ((concurrency === 'optimistic') || (concurrency === 'skipOnConflict')) { let oldValue = getOldValue(object, change.path); try { if (column && column.tsType === 'DateColumn') assertDatesEqual(oldValue, expectedOldValue); else assertDeepEqual(oldValue, expectedOldValue); } catch (e) { if (concurrency === 'skipOnConflict') return false; throw new Error('The row was changed by another user.'); } } return true; }); function getOldValue(obj, path) { let splitPath = path.split('/'); splitPath.shift(); return splitPath.reduce(extract, obj); function extract(obj, name) { if (obj === Object(obj)) return obj[name]; return; } } } } function assertDatesEqual(a, b) { const dateA = normalizeDateForCompare(a); const dateB = normalizeDateForCompare(b); if (dateA !== undefined && dateB !== undefined) { if (dateA !== dateB) throw new Error('A, b are not equal'); return; } assertDeepEqual(a, b); } function normalizeDateForCompare(value) { if (value == null) return value; if (value instanceof Date) return value.getTime(); if (typeof value !== 'string') return undefined; let normalized = value.replace(' ', 'T'); if (/[+-]\d{2}$/.test(normalized)) normalized += ':00'; else if (/^\d{4}-\d{2}-\d{2}T/.test(normalized) && !/(Z|[+-]\d{2}:?\d{2})$/.test(normalized)) normalized += 'Z'; const time = Date.parse(normalized); return Number.isNaN(time) ? undefined : time; } function assertDeepEqual(a, b) { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error('A, b are not equal'); } module.exports = applyPatch;