@syncable/core
Version:
245 lines • 9.89 kB
JavaScript
;
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