<commit_before>namespace('Sy.Storage');

/**
 * Bridge between database driver and entity level
 *
 * @package Sy
 * @subpackage Storage
 * @class
 */

Sy.Storage.UnitOfWork = function () {
    this.driver = null;
    this.map = null;
    this.entities = null;
    this.states = null;
    this.propertyAccessor = null;
    this.scheduledForInsert = [];
    this.scheduledForUpdate = [];
    this.scheduledForDelete = [];
    this.logger = null;
    this.generator = null;
};
Sy.Storage.UnitOfWork.prototype = Object.create(Object.prototype, {

    STATE_NEW: {
        value: 'new',
        writable: false,
    },

    STATE_MANAGED: {
        value: 'managed',
        writable: false
    },

    STATE_DETACHED: {
        value: 'detached',
        writable: false
    },

    STATE_REMOVED: {
        value: 'removed',
        writable: false
    },

    /**
     * Set the database driver
     *
     * @param {Sy.Storage.Dbal.DriverInterface} driver
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setDriver: {
        value: function (driver) {
            if (!(driver instanceof Sy.Storage.Dbal.DriverInterface)) {
                throw new TypeError('Invalid database driver');
            }

            this.driver = driver;

            return this;
        }
    },

    /**
     * Set the identity map
     *
     * @param {Sy.Storage.IdentityMap} map
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setIdentityMap: {
        value: function (map) {
            if (!(map instanceof Sy.Storage.IdentityMap)) {
                throw new TypeError('Invalid identity map');
            }

            this.map = map;

            return this;
        }
    },

    /**
     * Return the identity map
     *
     * @return {Sy.Storage.IdentityMap}
     */

    getIdentityMap: {
        value: function () {
            return this.map;
        }
    },

    /**
     * Set a state registry to hold loaded entities
     *
     * @param {Sy.StateRegistryInterface} registry
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setEntitiesRegistry: {
        value: function (registry) {
            if (!(registry instanceof Sy.StateRegistryInterface)) {
                throw new TypeError('Invalid state registry');
            }

            this.entities = registry;

            return this;
        }
    },

    /**
     * Set a property accessor
     *
     * @param {Sy.PropertyAccessor} accessor
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setPropertyAccessor: {
        value: function (accessor) {
            if (!(accessor instanceof Sy.PropertyAccessor)) {
                throw new TypeError('Invalid property accessor');
            }

            this.propertyAccessor = accessor;

            return this;
        }
    },

    /**
     * Set a state registry to hold entities states
     *
     * @param {Sy.StateRegistryInterface} registry
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setStatesRegistry: {
        value: function (registry) {
            if (!(registry instanceof Sy.StateRegistryInterface)) {
                throw new TypeError('Invalid state registry');
            }

            this.states = registry;

            return this;
        }
    },

    /**
     * Set a logger
     *
     * @param {Sy.Lib.Logger.Interface} logger
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setLogger: {
        value: function (logger) {
            if (!(logger instanceof Sy.Lib.Logger.Interface)) {
                throw new TypeError('Invalid logger');
            }

            this.logger = logger;

            return this;
        }
    },

    /**
     * Set a generator
     *
     * @param {Sy.Lib.Generator.Interface} generator
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setLogger: {
        value: function (generator) {
            if (!(generator instanceof Sy.Lib.Generator.Interface)) {
                throw new TypeError('Invalid generator');
            }

            this.generator = generator;

            return this;
        }
    },

    /**
     * Find an entity for the specified id
     *
     * @param {String} alias
     * @param {String} id
     *
     * @return {Promise}
     */

    find: {
        value: function (alias, id) {
            if (this.entities.has(alias, id)) {
                return new Promise(function (resolve) {
                    setTimeout(function () {
                        resolve(this.entities.get(alias, id));
                    }.bind(this), 0);
                }.bind(this));
            }

            return this.driver
                .read(alias, id)
                .then(function (data) {
                    return this.buildEntity(alias, data);
                }.bind(this));
        }
    },

    /**
     * Find all the entities for the given alias
     *
     * @param {String} alias
     *
     * @return {Promise}
     */

    findAll: {
        value: function (alias) {
            return this.driver
                .findAll(alias)
                .then(function (data) {
                    for (var i = 0, l = data.length; i < l; i++) {
                        data[i] = this.buildEntity(alias, data[i]);
                    }

                    return data;
                }.bind(this));
        }
    },

    /**
     * Find entities matching the given alias and criteria
     *
     * @param {String} alias
     * @param {String} index
     * @param {mixed} value
     * @param {Integer} limit
     *
     * @return {Promise}
     */

    findBy: {
        value: function (alias, index, value, limit) {
            return this.driver
                .findBy(alias, index, value, limit)
                .then(function (data) {
                    for (var i = 0, l = data.length; i < l; i++) {
                        data[i] = this.buildEntity(alias, data[i]);
                    }

                    return data;
                }.bind(this));
        }
    },

    /**
     * Build an entity for the specified alias
     *
     * @param {String} alias
     * @param {Object} data
     *
     * @return {Sy.EntityInterface}
     */

    buildEntity: {
        value: function (alias, data) {
            var constructor = this.map.getConstructor(alias),
                key = this.map.getKey(alias),
                entity;

            if (this.entities.has(alias, data[key])) {
                entity = this.entities.get(alias, data[key]);
            } else {
                entity = new constructor();
                this.entities.set(alias, data[key], entity);

                (new ObjectObserver(entity))
                    .open(function () {
                        this.computeSchedules(entity);
                    }.bind(this));
            }

            for (var p in data) {
                if (data.hasOwnProperty(p)) {
                    entity.set(p, data[p]);
                }
            }

            this.states.set(this.STATE_MANAGED, data[key], data[key]);

            return entity;
        }
    },

    /**
     * Plan the given entity to be persisted
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    persist: {
        value: function (entity) {
            if (!(entity instanceof Sy.EntityInterface)) {
                throw new TypeError('Invalid entity');
            }

            var alias = this.map.getAlias(entity),
                key = this.map.getKey(alias),
                id = this.propertyAccessor.getValue(entity, key),
                state = this.states.state(id);

            this.entities.set(alias, id, entity);

            if (state === undefined) {
                this.states.set(
                    this.STATE_NEW,
                    id,
                    id
                );
                this.scheduledForInsert.push(entity);

                (new ObjectObserver(entity))
                    .open(function () {
                        this.computeSchedules(entity);
                    }.bind(this));
            } else {
                this.scheduledForUpdate.push(entity);
            }

            return this;
        }
    },

    /**
     * Commit the changes to the database
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    commit: {
        value: function () {
            Platform.performMicrotaskCheckpoint();

            return this;
        }
    },

    /**
     * Plan the entity to be removed from the database
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    remove: {
        value: function (entity) {
            if (!(entity instanceof Sy.EntityInterface)) {
                throw new TypeError('Invalid entity');
            }

            this.scheduledForDelete.push(entity);

            return this;
        }
    },

    /**
     * Detach all entities or the ones of the given alias
     *
     * @param {String} alias optional
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    clear: {
        value: function (alias) {}
    },

    /**
     * Detach the given entity
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    detach: {
        value: function (entity) {}
    },

    /**
     * Check if the entity is managed
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Boolean}
     */

    isManaged: {
        value: function (entity) {
            var alias = this.map.getAlias(entity),
                key = this.map.getKey(alias),
                id = this.propertyAccessor.getValue(entity, key);
                state = this.states.state(id);

            return [this.STATE_NEW, this.STATE_MANAGED].indexOf(state) !== -1;
        }
    },

    /**
     * Check if the entity is scheduled for insertion
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Boolean}
     */

    isScheduledForInsert: {
        value: function (entity) {
            return this.scheduledForInsert.indexOf(entity) !== -1;
        }
    },

    /**
     * Check if the entity is scheduled for update
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Boolean}
     */

    isScheduledForUpdate: {
        value: function (entity) {
            return this.scheduledForUpdate.indexOf(entity) !== -1;
        }
    },

    /**
     * Check if the entity is scheduled for removal
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Boolean}
     */

    isScheduledForDelete: {
        value: function (entity) {
            return this.scheduledForDelete.indexOf(entity) !== -1;
        }
    },

    /**
     * Update the schedules for the given entity
     * If entity scheduled for insert leave as is
     * otherwise plan for update except if planned for delete
     *
     * @param {Sy.EntityInterface} entity
     */

    computeSchedules: {
        value: function (entity) {
            var alias = this.map.getAlias(entity),
                key = this.map.getKey(alias),
                state = this.states.state(
                    this.propertyAccessor.getValue(entity, key)
                );

            if (state === this.STATE_MANAGED && !this.isScheduledForUpdate(entity)) {
                this.scheduledForUpdate.push(entity);
            }
        }
    }

});
<commit_msg>handle the case where entity is scheduled for insert/update when calling removal<commit_after>namespace('Sy.Storage');

/**
 * Bridge between database driver and entity level
 *
 * @package Sy
 * @subpackage Storage
 * @class
 */

Sy.Storage.UnitOfWork = function () {
    this.driver = null;
    this.map = null;
    this.entities = null;
    this.states = null;
    this.propertyAccessor = null;
    this.scheduledForInsert = [];
    this.scheduledForUpdate = [];
    this.scheduledForDelete = [];
    this.logger = null;
    this.generator = null;
};
Sy.Storage.UnitOfWork.prototype = Object.create(Object.prototype, {

    STATE_NEW: {
        value: 'new',
        writable: false,
    },

    STATE_MANAGED: {
        value: 'managed',
        writable: false
    },

    STATE_DETACHED: {
        value: 'detached',
        writable: false
    },

    STATE_REMOVED: {
        value: 'removed',
        writable: false
    },

    /**
     * Set the database driver
     *
     * @param {Sy.Storage.Dbal.DriverInterface} driver
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setDriver: {
        value: function (driver) {
            if (!(driver instanceof Sy.Storage.Dbal.DriverInterface)) {
                throw new TypeError('Invalid database driver');
            }

            this.driver = driver;

            return this;
        }
    },

    /**
     * Set the identity map
     *
     * @param {Sy.Storage.IdentityMap} map
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setIdentityMap: {
        value: function (map) {
            if (!(map instanceof Sy.Storage.IdentityMap)) {
                throw new TypeError('Invalid identity map');
            }

            this.map = map;

            return this;
        }
    },

    /**
     * Return the identity map
     *
     * @return {Sy.Storage.IdentityMap}
     */

    getIdentityMap: {
        value: function () {
            return this.map;
        }
    },

    /**
     * Set a state registry to hold loaded entities
     *
     * @param {Sy.StateRegistryInterface} registry
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setEntitiesRegistry: {
        value: function (registry) {
            if (!(registry instanceof Sy.StateRegistryInterface)) {
                throw new TypeError('Invalid state registry');
            }

            this.entities = registry;

            return this;
        }
    },

    /**
     * Set a property accessor
     *
     * @param {Sy.PropertyAccessor} accessor
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setPropertyAccessor: {
        value: function (accessor) {
            if (!(accessor instanceof Sy.PropertyAccessor)) {
                throw new TypeError('Invalid property accessor');
            }

            this.propertyAccessor = accessor;

            return this;
        }
    },

    /**
     * Set a state registry to hold entities states
     *
     * @param {Sy.StateRegistryInterface} registry
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setStatesRegistry: {
        value: function (registry) {
            if (!(registry instanceof Sy.StateRegistryInterface)) {
                throw new TypeError('Invalid state registry');
            }

            this.states = registry;

            return this;
        }
    },

    /**
     * Set a logger
     *
     * @param {Sy.Lib.Logger.Interface} logger
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setLogger: {
        value: function (logger) {
            if (!(logger instanceof Sy.Lib.Logger.Interface)) {
                throw new TypeError('Invalid logger');
            }

            this.logger = logger;

            return this;
        }
    },

    /**
     * Set a generator
     *
     * @param {Sy.Lib.Generator.Interface} generator
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    setLogger: {
        value: function (generator) {
            if (!(generator instanceof Sy.Lib.Generator.Interface)) {
                throw new TypeError('Invalid generator');
            }

            this.generator = generator;

            return this;
        }
    },

    /**
     * Find an entity for the specified id
     *
     * @param {String} alias
     * @param {String} id
     *
     * @return {Promise}
     */

    find: {
        value: function (alias, id) {
            if (this.entities.has(alias, id)) {
                return new Promise(function (resolve) {
                    setTimeout(function () {
                        resolve(this.entities.get(alias, id));
                    }.bind(this), 0);
                }.bind(this));
            }

            return this.driver
                .read(alias, id)
                .then(function (data) {
                    return this.buildEntity(alias, data);
                }.bind(this));
        }
    },

    /**
     * Find all the entities for the given alias
     *
     * @param {String} alias
     *
     * @return {Promise}
     */

    findAll: {
        value: function (alias) {
            return this.driver
                .findAll(alias)
                .then(function (data) {
                    for (var i = 0, l = data.length; i < l; i++) {
                        data[i] = this.buildEntity(alias, data[i]);
                    }

                    return data;
                }.bind(this));
        }
    },

    /**
     * Find entities matching the given alias and criteria
     *
     * @param {String} alias
     * @param {String} index
     * @param {mixed} value
     * @param {Integer} limit
     *
     * @return {Promise}
     */

    findBy: {
        value: function (alias, index, value, limit) {
            return this.driver
                .findBy(alias, index, value, limit)
                .then(function (data) {
                    for (var i = 0, l = data.length; i < l; i++) {
                        data[i] = this.buildEntity(alias, data[i]);
                    }

                    return data;
                }.bind(this));
        }
    },

    /**
     * Build an entity for the specified alias
     *
     * @param {String} alias
     * @param {Object} data
     *
     * @return {Sy.EntityInterface}
     */

    buildEntity: {
        value: function (alias, data) {
            var constructor = this.map.getConstructor(alias),
                key = this.map.getKey(alias),
                entity;

            if (this.entities.has(alias, data[key])) {
                entity = this.entities.get(alias, data[key]);
            } else {
                entity = new constructor();
                this.entities.set(alias, data[key], entity);

                (new ObjectObserver(entity))
                    .open(function () {
                        this.computeSchedules(entity);
                    }.bind(this));
            }

            for (var p in data) {
                if (data.hasOwnProperty(p)) {
                    entity.set(p, data[p]);
                }
            }

            this.states.set(this.STATE_MANAGED, data[key], data[key]);

            return entity;
        }
    },

    /**
     * Plan the given entity to be persisted
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    persist: {
        value: function (entity) {
            if (!(entity instanceof Sy.EntityInterface)) {
                throw new TypeError('Invalid entity');
            }

            var alias = this.map.getAlias(entity),
                key = this.map.getKey(alias),
                id = this.propertyAccessor.getValue(entity, key),
                state = this.states.state(id);

            this.entities.set(alias, id, entity);

            if (state === undefined) {
                this.states.set(
                    this.STATE_NEW,
                    id,
                    id
                );
                this.scheduledForInsert.push(entity);

                (new ObjectObserver(entity))
                    .open(function () {
                        this.computeSchedules(entity);
                    }.bind(this));
            } else {
                this.scheduledForUpdate.push(entity);
            }

            return this;
        }
    },

    /**
     * Commit the changes to the database
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    commit: {
        value: function () {
            Platform.performMicrotaskCheckpoint();

            return this;
        }
    },

    /**
     * Plan the entity to be removed from the database
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    remove: {
        value: function (entity) {
            if (!(entity instanceof Sy.EntityInterface)) {
                throw new TypeError('Invalid entity');
            }

            var alias = this.map.getAlias(entity),
                key = this.map.getKey(alias),
                state = this.states.state(
                    this.propertyAccessor.getValue(entity, key)
                );

            if (state === this.STATE_MANAGED) {
                if (this.isScheduledForUpdate(entity)) {
                    this.scheduledForUpdate.splice(
                        this.scheduledForUpdate.indexOf(entity),
                        1
                    );
                }

                this.scheduledForDelete.push(entity);
            } else if (state === this.STATE_NEW) {
                this.scheduledForInsert.splice(
                    this.scheduledForInsert.indexOf(entity),
                    1
                );
            }

            return this;
        }
    },

    /**
     * Detach all entities or the ones of the given alias
     *
     * @param {String} alias optional
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    clear: {
        value: function (alias) {}
    },

    /**
     * Detach the given entity
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Sy.Storage.UnitOfWork} self
     */

    detach: {
        value: function (entity) {}
    },

    /**
     * Check if the entity is managed
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Boolean}
     */

    isManaged: {
        value: function (entity) {
            var alias = this.map.getAlias(entity),
                key = this.map.getKey(alias),
                id = this.propertyAccessor.getValue(entity, key);
                state = this.states.state(id);

            return [this.STATE_NEW, this.STATE_MANAGED].indexOf(state) !== -1;
        }
    },

    /**
     * Check if the entity is scheduled for insertion
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Boolean}
     */

    isScheduledForInsert: {
        value: function (entity) {
            return this.scheduledForInsert.indexOf(entity) !== -1;
        }
    },

    /**
     * Check if the entity is scheduled for update
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Boolean}
     */

    isScheduledForUpdate: {
        value: function (entity) {
            return this.scheduledForUpdate.indexOf(entity) !== -1;
        }
    },

    /**
     * Check if the entity is scheduled for removal
     *
     * @param {Sy.EntityInterface} entity
     *
     * @return {Boolean}
     */

    isScheduledForDelete: {
        value: function (entity) {
            return this.scheduledForDelete.indexOf(entity) !== -1;
        }
    },

    /**
     * Update the schedules for the given entity
     * If entity scheduled for insert leave as is
     * otherwise plan for update except if planned for delete
     *
     * @param {Sy.EntityInterface} entity
     */

    computeSchedules: {
        value: function (entity) {
            var alias = this.map.getAlias(entity),
                key = this.map.getKey(alias),
                state = this.states.state(
                    this.propertyAccessor.getValue(entity, key)
                );

            if (state === this.STATE_MANAGED && !this.isScheduledForUpdate(entity)) {
                this.scheduledForUpdate.push(entity);
            }
        }
    }

});

{'fraction_non_alphanumeric': 0.11376108062473618, 'fraction_numerical': 0.000675390460109751, 'mean_word_length': 1.4007904337251722, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2328470', 'n_tokens_mistral': 6600, 'n_tokens_neox': 6286, 'n_words': 1469}