UNPKG

@syncable/core

Version:
245 lines 9.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const lodash_1 = tslib_1.__importDefault(require("lodash")); const diff_patcher_1 = require("../diff-patcher"); const syncable_1 = require("../syncable"); class ChangePlant { constructor(blueprint) { this.blueprint = blueprint; } resolve({ type, refs: refDict, options }, syncables) { let processorOptions = this.blueprint[type]; let resolver = typeof processorOptions === 'object' ? processorOptions.resolver : undefined; if (!resolver) { return []; } let syncableMap = new Map(syncables.map((syncable) => [ syncable_1.getSyncableKey(syncable), syncable, ])); let syncableDict = {}; for (let [name, ref] of Object.entries(refDict)) { if (!ref) { continue; } if (Array.isArray(ref)) { syncableDict[name] = ref.map(ref => syncableMap.get(syncable_1.getSyncableKey(ref))); } else { syncableDict[name] = syncableMap.get(syncable_1.getSyncableKey(ref)); } } return resolver(syncableDict, options); } process({ id, type, refs: refDict, options, createdAt }, context, container, clock) { var _a; options = lodash_1.default.cloneDeep(options); let now = context.environment === 'client' ? createdAt : Date.now(); let preparedSyncableObjectMap = new Map(); let preparedSyncableObjectToSyncableMap = new Map(); let syncableObjectToFieldNameToGrantedRightsMapMap = new Map(); let preparedBundles = []; let creations = []; let removals = []; let removalObjectSet = new Set(); let updates = []; let notifications = []; let changes = []; let create = creation => { if (clock !== undefined) { creation._clock = clock; } creation._createdAt = now; creation._updatedAt = now; creations.push(creation); }; let remove = object => { object.validateAccessRights(['full'], context); removals.push(object.ref); removalObjectSet.add(object); }; let isBeingRemoved = object => { return removalObjectSet.has(object); }; let grant = (object, fieldNames, rights) => { let fieldNameToGrantedRightsMap = syncableObjectToFieldNameToGrantedRightsMapMap.get(object); if (!fieldNameToGrantedRightsMap) { fieldNameToGrantedRightsMap = new Map(); syncableObjectToFieldNameToGrantedRightsMapMap.set(object, fieldNameToGrantedRightsMap); } for (let fieldName of fieldNames) { fieldNameToGrantedRightsMap.set(fieldName, lodash_1.default.union(fieldNameToGrantedRightsMap.get(fieldName), rights)); } }; let prepare = object => { let clone = preparedSyncableObjectToSyncableMap.get(object); if (clone) { return clone; } object.validateAccessRights(['read'], context); let latest = object.syncable; clone = lodash_1.default.cloneDeep(latest); preparedBundles.push({ latest, clone, object, }); let key = syncable_1.getSyncableKey(object.ref); preparedSyncableObjectMap.set(key, object); preparedSyncableObjectToSyncableMap.set(object, clone); return clone; }; let notify = notification => { notifications.push(notification); }; let clonedSyncableOrCreationRefDict = {}; let syncableObjectDict = {}; for (let [name, ref] of Object.entries(refDict)) { if (!ref) { continue; } if ('id' in ref) { let object = container.requireSyncableObject(ref); clonedSyncableOrCreationRefDict[name] = prepare(object); syncableObjectDict[name] = object; } else if (Array.isArray(ref)) { let objects = ref .filter(ref => 'id' in ref) .map(ref => container.requireSyncableObject(ref)); clonedSyncableOrCreationRefDict[name] = objects.map(object => prepare(object)); syncableObjectDict[name] = objects; } else { clonedSyncableOrCreationRefDict[name] = ref; } } let processor = this.blueprint[type]; if (typeof processor === 'object') { processor = processor.processor; } let aborted = false; let abort = () => { aborted = true; }; let change = subsequentChange => { changes.push(subsequentChange); }; processor(clonedSyncableOrCreationRefDict, syncableObjectDict, { context, container, create, remove, isBeingRemoved, grant, prepare, notify, abort, change, type, options, refs: refDict, createdAt: now, }); if (aborted) { return { id, updates: [], creations: [], removals: [], notifications, changes, }; } let updatedContainer = new syncable_1.SyncableContainer(container.adapter); for (let object of container.getSyncableObjects()) { if (removalObjectSet.has(object)) { continue; } let syncable = (_a = preparedSyncableObjectToSyncableMap.get(object), (_a !== null && _a !== void 0 ? _a : object.syncable)); updatedContainer.addSyncable(syncable); } for (let createdSyncable of creations) { updatedContainer.addSyncable(createdSyncable); } for (let { latest: latestSyncable, clone: updatedSyncableClone, object: latestSyncableObject, } of preparedBundles) { if (removalObjectSet.has(latestSyncableObject)) { continue; } if (clock !== undefined) { updatedSyncableClone._clock = clock; } updatedSyncableClone._updatedAt = now; let syncableOverrides = updatedContainer .requireSyncableObject(latestSyncableObject.ref) .getSyncableOverrides(); Object.assign(updatedSyncableClone, syncableOverrides); let overriddenFieldNames = Object.keys(syncableOverrides); let delta = diff_patcher_1.diff(latestSyncable, updatedSyncableClone) || {}; let changedFieldNameSet = new Set(Object.keys(delta)); changedFieldNameSet.delete('_clock'); changedFieldNameSet.delete('_updatedAt'); if (!changedFieldNameSet.size) { continue; } if (changedFieldNameSet.has('_id') || changedFieldNameSet.has('_type') || changedFieldNameSet.has('_extends')) { throw new Error('Invalid operation'); } let requiredRightSet = new Set(['write']); let securingFieldNameSet = new Set(latestSyncableObject.getSecuringFieldNames()); for (let fieldName of changedFieldNameSet) { if (/^_/.test(fieldName) || securingFieldNameSet.has(fieldName)) { requiredRightSet.add('full'); break; } } let nonOverriddenChangedFieldNames = lodash_1.default.difference(Array.from(changedFieldNameSet), overriddenFieldNames); let fieldNameToGrantedRightsMap = syncableObjectToFieldNameToGrantedRightsMapMap.get(latestSyncableObject); if (fieldNameToGrantedRightsMap) { for (let requiredRight of requiredRightSet) { let fieldNames = nonOverriddenChangedFieldNames.filter(fieldName => { let grantedRights = fieldNameToGrantedRightsMap.get(fieldName); if (grantedRights) { // If access right is granted to this field, return false to ignore this field. return !grantedRights.includes(requiredRight); } else { return true; } }); if (fieldNames.length) { latestSyncableObject.validateAccessRights([requiredRight], context, fieldNames); } } } else { latestSyncableObject.validateAccessRights(Array.from(requiredRightSet), context, nonOverriddenChangedFieldNames); } updates.push({ delta, snapshot: updatedSyncableClone }); } for (let createdSyncable of creations) { let syncableOverrides = updatedContainer .requireSyncableObject(syncable_1.getSyncableRef(createdSyncable)) .getSyncableOverrides(); Object.assign(createdSyncable, syncableOverrides); } return { id, clock, updates, creations: creations || [], removals: removals || [], notifications, changes, }; } } exports.ChangePlant = ChangePlant; //# sourceMappingURL=change-plant.js.map