<commit_before>danceVideos = danceVideos.concat([
  {
    'title' : "Tango: Walk",
    'description' : 'Demonstrated by William Pino.',
    'labels' : [],
    'url' : 'http://www.youtube.com/v/wHkgUOLFtRw'
  },
  {
    'title' : "Tango: Back Open Promenade",
    'description' : 'Demonstrated by William Pino.',
    'labels' : ['BackOpenPromenade'],
    'url' : 'http://www.youtube.com/v/2DvkG3il_Ks'
  },
  {
    'title' : "Tango: Routine",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com. Routine:
 ' +
      '
 Walk ' +
      '
 Progressive Link ' +
      '
 Open Promenade ' +
      '
  Outside Swivel ' +
      '
  Promenade Link ' +
      '
  Brush Tap ' +
      '
  Closed Promenade' +
      '
',
    'labels' : ['ProgressiveLink', 'OpenPromenade', 'OutiseSwivel', 'PromenadeLink', 'ClosedPromenade'],
    'url' : 'http://www.youtube.com/v/sS0dJ54A9jQ'
  },
  {
    'title' : "Tango Technique: Tango Walk, Progressive Side Step",
    'description' : 'Demonstrated by Monika.',
    'labels' : ['ProgressiveSideStep'],
    'url' : 'http://www.youtube.com/v/C6KBqf7fb_M'
  },
  {
    'title' : "Tango: Open Reverse Turn Lady Outside",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com',
    'labels' : ['OpenReverseTurnLadyOutside'],
    'url' : 'http://www.youtube.com/v/MfumvQ0e2Zs'
  },
  {
    'title' : "Tango: Natural Promenade Turn, Rock Turn",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com',
    'labels' : ['NaturalPromenadeTurn', 'RockTurn'],
    'url' : 'http://www.youtube.com/v/p0XJQ5vJswU'
  },
  {
    'title' : "Tango: Rock Turn - Lady's steps",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['RockTurn'],
    'url' : 'http://www.youtube.com/v/8aHEtsgs1ws'
  },
  {
    'title' : "Tango: Rock Turn - with partner",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['RockTurn'],
    'url' : 'http://www.youtube.com/v/y1kLvsfKnbQ'
  },
  {
    'title' : "Tango: Rock Turn - Man's steps",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['RockTurn'],
    'url' : 'http://www.youtube.com/v/Tov3-0p8dto'
  },
  {
    'title' : "Tango: Open Promenade",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['OpenPromenade'],
    'url' : 'http://www.youtube.com/v/OcWVOD03OsM'
  },
  {
    'title' : "Tango: Progressive Link",
    'description' : 'Demonstrated by Victor & Header',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/vf3b7X8pv-4'
  },
  {
    'title' : "Tango: Progressive Link & Head Flick",
    'description' : 'Demonstrated by Marcus & Karen Hilton.',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/swjrEwMnwHI'
  },
  {
    'title' : "Tango: Progressive Link, Open Promenade",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com',
    'labels' : ['ProgressiveLink', 'OpenPromenade'],
    'url' : 'http://www.youtube.com/v/vOdwVSgTBFs'
  },
  {
    'title' : "Tango: Walk, Progressive Link, Closed Promenade",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com',
    'labels' : ['ProgressiveLink', 'ClosedPromenade'],
    'url' : 'http://www.youtube.com/v/hEH7m_t5erQ'
  },
  {
    'title' : "Tango: Walk, Progressive Link - with partner & music",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/P4tdbZZdhzM'
  },
  {
    'title' : "Tango: Walk, Progressive Link - Lady's steps",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/PoaRzMZm8kQ'
  },
  {
    'title' : "Tango: Walk, Progressive Link - with partner",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/EQT3ztp9l9A'
  },
  {
    'title' : "Tango: Walk, Progressive Link - Man's steps",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/qzCf0Tw6hOs'
  }
]);

<commit_msg>added video for fallaway promenade and natural twist turn<commit_after>danceVideos = danceVideos.concat([
  {
    'title' : "Tango: Walk",
    'description' : 'Demonstrated by William Pino.',
    'labels' : [],
    'url' : 'http://www.youtube.com/v/wHkgUOLFtRw'
  },
  {
    'title' : "Tango: Back Open Promenade",
    'description' : 'Demonstrated by William Pino.',
    'labels' : ['BackOpenPromenade'],
    'url' : 'http://www.youtube.com/v/2DvkG3il_Ks'
  },
  {
    'title' : "Tango: Routine",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com. Routine:
 ' +
      '
 Walk ' +
      '
 Progressive Link ' +
      '
 Open Promenade ' +
      '
  Outside Swivel ' +
      '
  Promenade Link ' +
      '
  Brush Tap ' +
      '
  Closed Promenade' +
      '
',
    'labels' : ['ProgressiveLink', 'OpenPromenade', 'OutiseSwivel', 'PromenadeLink', 'ClosedPromenade'],
    'url' : 'http://www.youtube.com/v/sS0dJ54A9jQ'
  },
  {
    'title' : "Tango Technique: Tango Walk, Progressive Side Step",
    'description' : 'Demonstrated by Monika.',
    'labels' : ['ProgressiveSideStep'],
    'url' : 'http://www.youtube.com/v/C6KBqf7fb_M'
  },
  {
    'title' : "Tango: Open Reverse Turn Lady Outside",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com',
    'labels' : ['OpenReverseTurnLadyOutside'],
    'url' : 'http://www.youtube.com/v/MfumvQ0e2Zs'
  },
  {
    'title' : "Tango: Natural Promenade Turn, Rock Turn",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com',
    'labels' : ['NaturalPromenadeTurn', 'RockTurn'],
    'url' : 'http://www.youtube.com/v/p0XJQ5vJswU'
  },
  {
    'title' : "Tango: Rock Turn - Lady's steps",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['RockTurn'],
    'url' : 'http://www.youtube.com/v/8aHEtsgs1ws'
  },
  {
    'title' : "Tango: Rock Turn - with partner",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['RockTurn'],
    'url' : 'http://www.youtube.com/v/y1kLvsfKnbQ'
  },
  {
    'title' : "Tango: Rock Turn - Man's steps",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['RockTurn'],
    'url' : 'http://www.youtube.com/v/Tov3-0p8dto'
  },
  {
    'title' : "Tango: Open Promenade",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['OpenPromenade'],
    'url' : 'http://www.youtube.com/v/OcWVOD03OsM'
  },
  {
    'title' : "Tango: Progressive Link",
    'description' : 'Demonstrated by Victor & Header',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/vf3b7X8pv-4'
  },
  {
    'title' : "Tango: Progressive Link & Head Flick",
    'description' : 'Demonstrated by Marcus & Karen Hilton.',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/swjrEwMnwHI'
  },
  {
    'title' : "Tango: Progressive Link, Open Promenade",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com',
    'labels' : ['ProgressiveLink', 'OpenPromenade'],
    'url' : 'http://www.youtube.com/v/vOdwVSgTBFs'
  },
  {
    'title' : "Tango: Walk, Progressive Link, Closed Promenade",
    'description' : 'Demonstrated by Andy & Wendy Wong from grandballroom.com',
    'labels' : ['ProgressiveLink', 'ClosedPromenade'],
    'url' : 'http://www.youtube.com/v/hEH7m_t5erQ'
  },
  {
    'title' : "Tango: Walk, Progressive Link - with partner & music",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/P4tdbZZdhzM'
  },
  {
    'title' : "Tango: Walk, Progressive Link - Lady's steps",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/PoaRzMZm8kQ'
  },
  {
    'title' : "Tango: Walk, Progressive Link - with partner",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/EQT3ztp9l9A'
  },
  {
    'title' : "Tango: Walk, Progressive Link - Man's steps",
    'description' : 'Demonstrated by Sarolta Eke & Tamas Kassay',
    'labels' : ['ProgressiveLink'],
    'url' : 'http://www.youtube.com/v/qzCf0Tw6hOs'
  },
  {
    'title': "Tango: Walks and Progressive Link -- Fallaway Promenade -- Natural Twist Turn",
    'description' : 'Demonstrated by Andy and Wendy Wong from grandballroom.com',
    'labels' : ['FallawayPromenade','NaturalTwistTurn'],
    'url' : 'http://www.youtube.com/v/JodkXIcu4bU'
  }
]);

<|endoftext|>


<commit_before>'use strict';

const { crawlAttrs } = require('../crawl');
const { getThrowErr } = require('../error');
const { getOperator } = require('../operators');

const { getDeepAttr } = require('./attr');
const { getSiblingValue } = require('./siblings');
const { validators } = require('./validators');

// `attrs` must be `{ collname: { attrName:
// { type: 'string|number|integer|boolean', isArray: true|false } } }`
const validateFilter = function ({
  filter,
  attrs,
  reason = 'INPUT_VALIDATION',
  prefix = '',
  skipConfigFuncs,
}) {
  if (filter == null) { return; }

  const throwErr = getThrowErr.bind(null, { reason, prefix });

  crawlAttrs(
    filter,
    nodes => validateAttr({ nodes, attrs, skipConfigFuncs, throwErr }),
  );
};

const validateAttr = function ({ nodes, ...rest }) {
  nodes.forEach(node => validateNode({ node, operations: nodes, ...rest }));
};

const validateNode = function ({
  node,
  node: { type, attrName },
  operations,
  attrs,
  skipConfigFuncs,
  throwErr,
}) {
  const operator = getOperator({ node });

  const attr = getDeepAttr({ attrs, attrName, throwErr });

  const throwErrA = throwErr.bind(null, attrName);

  const valueA = getSiblingValue({ node, attrs, throwErr: throwErrA });

  validateValue({
    type,
    value: valueA,
    attr,
    operator,
    operations,
    skipConfigFuncs,
    throwErr: throwErrA,
  });
};

const validateValue = function ({
  type,
  value,
  attr,
  attr: { validation: attrValidate },
  operator: { validate: opValidate },
  operations,
  skipConfigFuncs,
  throwErr,
}) {
  // Skip config functions
  // If one wants to validate them, they need to be evaluated first
  if (skipConfigFuncs && typeof value === 'function') { return; }

  if (opValidate !== undefined) {
    opValidate({ type, value, attr, throwErr });
  }

  if (attrValidate) {
    Object.entries(attrValidate)
      .forEach(([keyword, ruleVal]) => validators[keyword]({
        type,
        value,
        ruleVal,
        validation: attrValidate,
        operations,
        throwErr,
      }));
  }
};

module.exports = {
  validateFilter,
};
<commit_msg>Refactoring<commit_after>'use strict';

const { crawlAttrs } = require('../crawl');
const { getThrowErr } = require('../error');
const { getOperator } = require('../operators');

const { getDeepAttr } = require('./attr');
const { getSiblingValue } = require('./siblings');
const { validators } = require('./validators');

// `attrs` must be `{ collname: { attrName:
// { type: 'string|number|integer|boolean', isArray: true|false } } }`
const validateFilter = function ({
  filter,
  attrs,
  reason = 'INPUT_VALIDATION',
  prefix = '',
  skipConfigFuncs,
}) {
  if (filter == null) { return; }

  const throwErr = getThrowErr.bind(null, { reason, prefix });

  crawlAttrs(
    filter,
    nodes => validateAttr({ nodes, attrs, skipConfigFuncs, throwErr }),
  );
};

const validateAttr = function ({ nodes, ...rest }) {
  nodes.forEach(node => validateNode({ node, operations: nodes, ...rest }));
};

const validateNode = function ({
  node,
  node: { type, attrName },
  operations,
  attrs,
  skipConfigFuncs,
  throwErr,
}) {
  const operator = getOperator({ node });

  const attr = getDeepAttr({ attrs, attrName, throwErr });

  const throwErrA = throwErr.bind(null, attrName);

  const valueA = getSiblingValue({ node, attrs, throwErr: throwErrA });

  validateValue({
    type,
    value: valueA,
    attr,
    operator,
    operations,
    skipConfigFuncs,
    throwErr: throwErrA,
  });
};

const validateValue = function ({
  type,
  value,
  attr,
  attr: { validation: attrValidate },
  operator: { validate: opValidate },
  operations,
  skipConfigFuncs,
  throwErr,
}) {
  // Skip config functions
  // If one wants to validate them, they need to be evaluated first
  if (skipConfigFuncs && typeof value === 'function') { return; }

  if (opValidate !== undefined) {
    opValidate({ type, value, attr, throwErr });
  }

  if (attrValidate !== undefined) {
    Object.entries(attrValidate)
      .forEach(([keyword, ruleVal]) => validators[keyword]({
        type,
        value,
        ruleVal,
        validation: attrValidate,
        operations,
        throwErr,
      }));
  }
};

module.exports = {
  validateFilter,
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationPredicate',
  properties: [
    {
      name: 'predicateFactory'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      expression: function(predicateFactory) {
        return predicateFactory ?
          predicateFactory(foam.mlang.ExpressionsSingleton.create()) :
          null;
      }
    },
    {
      class: 'StringArray',
      name: 'args'
    },
    {
      class: 'Function',
      name: 'jsFunc',
      expression: function(predicate, jsErr) {
        return function() {
          if ( ! predicate.f(this) ) return jsErr(this);
        };
      }
    },
    {
      class: 'String',
      name: 'errorString'
    },
    {
      class: 'Function',
      name: 'jsErr',
      expression: function(errorString) {
        return function() { return errorString; };
      }
    }
  ],
  methods: [
    function createErrorSlotFor(data) {
      return this.ExpressionSlot.create({
        args: this.args.map(a => data[a+'$']),
        code: this.jsFunc.bind(data)
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PropertyValidationRefinement',
  refines: 'foam.core.Property',

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates'
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates) {
        if ( validationPredicates.length ) {
          var args = foam.Array.unique(validationPredicates
            .map(vp => vp.args)
            .flat());
          return [args, function() {
            for ( var i = 0 ; i < validationPredicates.length ; i++ ) {
              var vp = validationPredicates[i];
              if ( vp.jsFunc.bind(this)() ) return vp.jsErr.bind(this)();
            }
            return null;
          }];
        }
        return !required ? null : [[name],
          function() {
            return !this.hasOwnProperty(name) && (label + ' is required.');
          }]
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'StringPropertyValidationRefinement',
  refines: 'foam.core.String',
  properties: [
    'minLength',
    'maxLength',
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), self.minLength);
            },
            errorString: `${this.label} must be at least ${this.minLength} character${this.minLength>1?'s':''}`
          });
        }
        if ( foam.Number.isInstance(this.maxLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.LTE(foam.mlang.StringLength.create({ arg1: self }), self.maxLength);
            },
            errorString: `${this.label} must be at most ${this.maxLength} character${this.maxLength>1?'s':''}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'FObjectPropertyValidationRefinement',
  refines: 'foam.core.FObjectProperty',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates, autoValidate) {
        if ( autoValidate ) {
          return [
            [`${name}$errors_`],
            function(errs) {
              return errs ? label + ' is invalid.' : null;
            }
          ];
        }
        return foam.core.Property.VALIDATE_OBJ.expression.apply(this, arguments);
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'Errors',
//  extends: 'foam.core.Property',

  documentation: `
    A psedo-Property Axiom added to FObject which contains an object\'s validation errors.
    Adds the following attributes to an Object:

errors_

list of current errors

errors_$

Slot representation of errors_

validateObject()

calls the validateObj() method of all property Axioms, allowing them to populate errors_

  `,

  properties: [
    [ 'name', 'errors_' ]
  ],

  methods: [
    function installInProto(proto) {
      var self = this;
      Object.defineProperty(proto, 'errors_', {
        get: function() {
          return self.toSlot(this).get();
        },
        configurable: true,
        enumerable: false
      });

      Object.defineProperty(proto, 'errors_$', {
        get: function() {
          return self.toSlot(this);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      var slotName = this.slotName_ || ( this.slotName_ = this.name + '$' );
      var slot     = obj.getPrivate_(slotName);

      if ( ! slot ) {
        slot = this.createErrorSlot_(obj)
        obj.setPrivate_(slotName, slot);
      }

      return slot;
    },

    function createErrorSlot_(obj) {
      var args = [];
      var ps   = obj.cls_.getAxiomsByClass(foam.core.Property).
        filter(function(a) { return a.validateObj; });

      for ( var i = 0 ; i < ps.length ; i++ ) {
        var p = ps[i];
        args.push(obj.slot(p.validateObj));
      }

      function validateObject() {
        var ret;

        for ( var i = 0 ; i < ps.length ; i++ ) {
          var p = ps[i];
          var err = args[i].get();
          if ( err ) (ret || (ret = [])).push([p, err]);
        }

        return ret;
      }

      return foam.core.ExpressionSlot.create({
        obj: obj,
        code: validateObject,
        args: args});
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationFObjectRefinement',
  refines: 'foam.core.FObject',

  axioms: [
    foam.core.internal.Errors.create()
  ]
});
<commit_msg>Add min/max auto cross platform validation to Int.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationPredicate',
  properties: [
    {
      name: 'predicateFactory'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      expression: function(predicateFactory) {
        return predicateFactory ?
          predicateFactory(foam.mlang.ExpressionsSingleton.create()) :
          null;
      }
    },
    {
      class: 'StringArray',
      name: 'args'
    },
    {
      class: 'Function',
      name: 'jsFunc',
      expression: function(predicate, jsErr) {
        return function() {
          if ( ! predicate.f(this) ) return jsErr(this);
        };
      }
    },
    {
      class: 'String',
      name: 'errorString'
    },
    {
      class: 'Function',
      name: 'jsErr',
      expression: function(errorString) {
        return function() { return errorString; };
      }
    }
  ],
  methods: [
    function createErrorSlotFor(data) {
      return this.ExpressionSlot.create({
        args: this.args.map(a => data[a+'$']),
        code: this.jsFunc.bind(data)
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PropertyValidationRefinement',
  refines: 'foam.core.Property',

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates'
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates) {
        if ( validationPredicates.length ) {
          var args = foam.Array.unique(validationPredicates
            .map(vp => vp.args)
            .flat());
          return [args, function() {
            for ( var i = 0 ; i < validationPredicates.length ; i++ ) {
              var vp = validationPredicates[i];
              if ( vp.jsFunc.bind(this)() ) return vp.jsErr.bind(this)();
            }
            return null;
          }];
        }
        return !required ? null : [[name],
          function() {
            return !this.hasOwnProperty(name) && (label + ' is required.');
          }]
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'StringPropertyValidationRefinement',
  refines: 'foam.core.String',
  properties: [
    'minLength',
    'maxLength',
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), self.minLength);
            },
            errorString: `${this.label} must be at least ${this.minLength} character${this.minLength>1?'s':''}`
          });
        }
        if ( foam.Number.isInstance(this.maxLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.LTE(foam.mlang.StringLength.create({ arg1: self }), self.maxLength);
            },
            errorString: `${this.label} must be at most ${this.maxLength} character${this.maxLength>1?'s':''}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'FObjectPropertyValidationRefinement',
  refines: 'foam.core.FObjectProperty',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates, autoValidate) {
        if ( autoValidate ) {
          return [
            [`${name}$errors_`],
            function(errs) {
              return errs ? label + ' is invalid.' : null;
            }
          ];
        }
        return foam.core.Property.VALIDATE_OBJ.expression.apply(this, arguments);
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'IntPropertyValidationRefinement',
  refines: 'foam.core.Int',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validationPredicates',
      factory: function() {
        if ( ! this.autoValidate ) return [];
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(self.min) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.GTE(self, self.min);
            },
            errorString: `${self.label} must be at least ${self.min}.`
          });
        }
        if ( foam.Number.isInstance(self.max) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.LTE(self, self.max);
            },
            errorString: `${self.label} must be at most ${self.max}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'Errors',
//  extends: 'foam.core.Property',

  documentation: `
    A psedo-Property Axiom added to FObject which contains an object\'s validation errors.
    Adds the following attributes to an Object:

errors_

list of current errors

errors_$

Slot representation of errors_

validateObject()

calls the validateObj() method of all property Axioms, allowing them to populate errors_

  `,

  properties: [
    [ 'name', 'errors_' ]
  ],

  methods: [
    function installInProto(proto) {
      var self = this;
      Object.defineProperty(proto, 'errors_', {
        get: function() {
          return self.toSlot(this).get();
        },
        configurable: true,
        enumerable: false
      });

      Object.defineProperty(proto, 'errors_$', {
        get: function() {
          return self.toSlot(this);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      var slotName = this.slotName_ || ( this.slotName_ = this.name + '$' );
      var slot     = obj.getPrivate_(slotName);

      if ( ! slot ) {
        slot = this.createErrorSlot_(obj)
        obj.setPrivate_(slotName, slot);
      }

      return slot;
    },

    function createErrorSlot_(obj) {
      var args = [];
      var ps   = obj.cls_.getAxiomsByClass(foam.core.Property).
        filter(function(a) { return a.validateObj; });

      for ( var i = 0 ; i < ps.length ; i++ ) {
        var p = ps[i];
        args.push(obj.slot(p.validateObj));
      }

      function validateObject() {
        var ret;

        for ( var i = 0 ; i < ps.length ; i++ ) {
          var p = ps[i];
          var err = args[i].get();
          if ( err ) (ret || (ret = [])).push([p, err]);
        }

        return ret;
      }

      return foam.core.ExpressionSlot.create({
        obj: obj,
        code: validateObject,
        args: args});
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationFObjectRefinement',
  refines: 'foam.core.FObject',

  axioms: [
    foam.core.internal.Errors.create()
  ]
});
<|endoftext|>


<commit_before>/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RelayStoreData
 * @flow
 * @typechecks
 */

'use strict';

const GraphQLQueryRunner = require('GraphQLQueryRunner');
const GraphQLStoreChangeEmitter = require('GraphQLStoreChangeEmitter');
const GraphQLStoreDataHandler = require('GraphQLStoreDataHandler');
const GraphQLStoreRangeUtils = require('GraphQLStoreRangeUtils');
const RelayChangeTracker = require('RelayChangeTracker');
import type {ChangeSet} from 'RelayChangeTracker';
const RelayConnectionInterface = require('RelayConnectionInterface');
import type {GarbageCollectionScheduler} from 'RelayGarbageCollector';
const RelayGarbageCollector = require('RelayGarbageCollector');
const RelayMutationQueue = require('RelayMutationQueue');
import type {
  ClientMutationID,
  DataID,
  NodeRangeMap,
  Records,
  RelayQuerySet,
  RootCallMap,
  UpdateOptions,
} from 'RelayInternalTypes';
const RelayNodeInterface = require('RelayNodeInterface');
const RelayPendingQueryTracker = require('RelayPendingQueryTracker');
const RelayProfiler = require('RelayProfiler');
const RelayQuery = require('RelayQuery');
const RelayQueryTracker = require('RelayQueryTracker');
const RelayQueryWriter = require('RelayQueryWriter');
const RelayRecordStore = require('RelayRecordStore');
import type {CacheManager, CacheReadCallbacks} from 'RelayTypes';

const forEachObject = require('forEachObject');
const invariant = require('invariant');
const generateForceIndex = require('generateForceIndex');
const readRelayDiskCache = require('readRelayDiskCache');
const warning = require('warning');
const writeRelayQueryPayload = require('writeRelayQueryPayload');
const writeRelayUpdatePayload = require('writeRelayUpdatePayload');

var {CLIENT_MUTATION_ID} = RelayConnectionInterface;
var {NODE_TYPE} = RelayNodeInterface;

// The source of truth for application data.
var _instance;

/**
 * @internal
 *
 * Wraps the data caches and associated metadata tracking objects used by
 * GraphQLStore/RelayStore.
 */
class RelayStoreData {
  _cacheManager: ?CacheManager;
  _cachedRecords: Records;
  _cachedRootCallMap: RootCallMap;
  _cachedStore: RelayRecordStore;
  _changeEmitter: GraphQLStoreChangeEmitter;
  _garbageCollector: ?RelayGarbageCollector;
  _mutationQueue: RelayMutationQueue;
  _nodeRangeMap: NodeRangeMap;
  _pendingQueryTracker: RelayPendingQueryTracker;
  _records: Records;
  _queuedRecords: Records;
  _queuedStore: RelayRecordStore;
  _recordStore: RelayRecordStore;
  _queryTracker: RelayQueryTracker;
  _queryRunner: GraphQLQueryRunner;
  _rangeData: GraphQLStoreRangeUtils;
  _rootCallMap: RootCallMap;

  /**
   * Get the data set backing actual Relay operations. Used in GraphQLStore.
   */
  static getDefaultInstance(): RelayStoreData {
    if (!_instance) {
      _instance = new RelayStoreData();
    }
    return _instance;
  }

  constructor() {
    const cachedRecords: Records = ({}: $FixMe);
    const cachedRootCallMap: RootCallMap = {};
    const queuedRecords: Records = ({}: $FixMe);
    const records: Records = ({}: $FixMe);
    const rootCallMap: RootCallMap = {};
    const nodeRangeMap: NodeRangeMap = ({}: $FixMe);
    const {
      cachedStore,
      queuedStore,
      recordStore,
    } = createRecordCollection({
      cachedRecords,
      cachedRootCallMap,
      cacheWriter: null,
      queuedRecords,
      nodeRangeMap,
      records,
      rootCallMap,
    });
    const rangeData = new GraphQLStoreRangeUtils();

    this._cacheManager = null;
    this._cachedRecords = cachedRecords;
    this._cachedRootCallMap = cachedRootCallMap;
    this._cachedStore = cachedStore;
    this._changeEmitter = new GraphQLStoreChangeEmitter(rangeData);
    this._mutationQueue = new RelayMutationQueue(this);
    this._nodeRangeMap = nodeRangeMap;
    this._pendingQueryTracker = new RelayPendingQueryTracker(this);
    this._queryRunner = new GraphQLQueryRunner(this);
    this._queryTracker = new RelayQueryTracker();
    this._queuedRecords = queuedRecords;
    this._queuedStore = queuedStore;
    this._records = records;
    this._recordStore = recordStore;
    this._rangeData = rangeData;
    this._rootCallMap = rootCallMap;
  }

  /**
   * Creates a garbage collector for this instance. After initialization all
   * newly added DataIDs will be registered in the created garbage collector.
   * This will show a warning if data has already been added to the instance.
   */
  initializeGarbageCollector(scheduler: GarbageCollectionScheduler): void {
    invariant(
      !this._garbageCollector,
      'RelayStoreData: Garbage collector is already initialized.'
    );
    var shouldInitialize = this._isStoreDataEmpty();
    warning(
      shouldInitialize,
      'RelayStoreData: Garbage collection can only be initialized when no ' +
      'data is present.'
    );
    if (shouldInitialize) {
      this._garbageCollector = new RelayGarbageCollector(this, scheduler);
    }
  }

  /**
   * Sets/clears the cache manager that is used to cache changes written to
   * the store.
   */
  injectCacheManager(cacheManager: ?CacheManager): void {
    const {
      cachedStore,
      queuedStore,
      recordStore,
    } = createRecordCollection({
      cachedRecords: this._cachedRecords,
      cachedRootCallMap: this._cachedRootCallMap,
      cacheWriter: cacheManager ? cacheManager.getQueryWriter() : null,
      queuedRecords: this._queuedRecords,
      nodeRangeMap: this._nodeRangeMap,
      records: this._records,
      rootCallMap: this._rootCallMap,
    });

    this._cacheManager = cacheManager;
    this._cachedStore = cachedStore;
    this._queuedStore = queuedStore;
    this._recordStore = recordStore;
  }

  clearCacheManager(): void {
    const {
      cachedStore,
      queuedStore,
      recordStore,
    } = createRecordCollection({
      cachedRecords: this._cachedRecords,
      cachedRootCallMap: this._cachedRootCallMap,
      cacheWriter: null,
      queuedRecords: this._queuedRecords,
      nodeRangeMap: this._nodeRangeMap,
      records: this._records,
      rootCallMap: this._rootCallMap,
    });

    this._cacheManager = null;
    this._cachedStore = cachedStore;
    this._queuedStore = queuedStore;
    this._recordStore = recordStore;
  }

  hasCacheManager(): boolean {
    return !!this._cacheManager;
  }

  /**
   * Returns whether a given record is affected by an optimistic update.
   */
  hasOptimisticUpdate(dataID: DataID): boolean {
    dataID = this.getRangeData().getCanonicalClientID(dataID);
    return this.getQueuedStore().hasOptimisticUpdate(dataID);
  }

  /**
   * Returns a list of client mutation IDs for queued mutations whose optimistic
   * updates are affecting the record corresponding the given dataID. Returns
   * null if the record isn't affected by any optimistic updates.
   */
  getClientMutationIDs(dataID: DataID): ?Array<ClientMutationID> {
    dataID = this.getRangeData().getCanonicalClientID(dataID);
    return this.getQueuedStore().getClientMutationIDs(dataID);
  }

  /**
   * Reads data for queries incrementally from disk cache.
   * It calls onSuccess when all the data has been loaded into memory.
   * It calls onFailure when some data is unabled to be satisfied from disk.
   */
  readFromDiskCache(
    queries: RelayQuerySet,
    callbacks: CacheReadCallbacks
  ): void {
    var cacheManager = this._cacheManager;
    invariant(
      cacheManager,
      'RelayStoreData: `readFromDiskCache` should only be called when cache ' +
      'manager is available.'
    );
    var profile = RelayProfiler.profile('RelayStoreData.readFromDiskCache');
    readRelayDiskCache(
      queries,
      this._queuedStore,
      this._cachedRecords,
      this._cachedRootCallMap,
      cacheManager,
      {
        onSuccess: () => {
          profile.stop();
          callbacks.onSuccess && callbacks.onSuccess();
        },
        onFailure: () => {
          profile.stop();
          callbacks.onFailure && callbacks.onFailure();
        },
      }
    );
  }

  /**
   * Write the results of a query into the base record store.
   */
  handleQueryPayload(
    query: RelayQuery.Root,
    response: {[key: string]: mixed},
    forceIndex: ?number
  ): void {
    var profiler = RelayProfiler.profile('RelayStoreData.handleQueryPayload');
    var changeTracker = new RelayChangeTracker();
    var writer = new RelayQueryWriter(
      this._recordStore,
      this._queryTracker,
      changeTracker,
      {
        forceIndex,
        updateTrackedQueries: true,
      }
    );
    writeRelayQueryPayload(
      writer,
      query,
      response
    );
    this._handleChangedAndNewDataIDs(changeTracker.getChangeSet());
    profiler.stop();
  }

  /**
   * Write the results of an update into the base record store.
   */
  handleUpdatePayload(
    operation: RelayQuery.Operation,
    payload: {[key: string]: mixed},
    {configs, isOptimisticUpdate}: UpdateOptions
  ): void {
    var profiler = RelayProfiler.profile('RelayStoreData.handleUpdatePayload');
    var changeTracker = new RelayChangeTracker();
    var store;
    if (isOptimisticUpdate) {
      var clientMutationID = payload[CLIENT_MUTATION_ID];
      invariant(
        typeof clientMutationID === 'string',
        'RelayStoreData.handleUpdatePayload(): Expected optimistic payload ' +
        'to have a valid `%s`.',
        CLIENT_MUTATION_ID
      );
      store = this.getRecordStoreForOptimisticMutation(clientMutationID);
    } else {
      store = this._getRecordStoreForMutation();
    }
    var writer = new RelayQueryWriter(
      store,
      this._queryTracker,
      changeTracker,
      {
        forceIndex: generateForceIndex(),
        isOptimisticUpdate,
        updateTrackedQueries: false,
      }
    );
    writeRelayUpdatePayload(
      writer,
      operation,
      payload,
      {configs, isOptimisticUpdate}
    );
    this._handleChangedAndNewDataIDs(changeTracker.getChangeSet());
    profiler.stop();
  }

  /**
   * Given a query fragment and a data ID, returns a root query that applies
   * the fragment to the object specified by the data ID.
   */
  buildFragmentQueryForDataID(
    fragment: RelayQuery.Fragment,
    dataID: DataID
  ): RelayQuery.Root {
    if (GraphQLStoreDataHandler.isClientID(dataID)) {
      const path = this._queuedStore.getPathToRecord(dataID);
      invariant(
        path,
        'RelayStoreData.buildFragmentQueryForDataID(): Cannot refetch ' +
        'record `%s` without a path.',
        dataID
      );
      return path.getQuery(fragment);
    }
    // Fragment fields cannot be spread directly into the root because they
    // may not exist on the `Node` type.
    return RelayQuery.Root.build(
      fragment.getDebugName() || 'UnknownQuery',
      RelayNodeInterface.NODE,
      dataID,
      [fragment],
      {identifyingArgName: RelayNodeInterface.ID},
      NODE_TYPE
    );
  }

  getNodeData(): Records {
    return this._records;
  }

  getQueuedData(): Records {
    return this._queuedRecords;
  }

  clearQueuedData(): void {
    forEachObject(this._queuedRecords, (_, key) => {
      delete this._queuedRecords[key];
      this._changeEmitter.broadcastChangeForID(key);
    });
  }

  getCachedData(): Records {
    return this._cachedRecords;
  }

  getGarbageCollector(): ?RelayGarbageCollector {
    return this._garbageCollector;
  }

  getMutationQueue(): RelayMutationQueue {
    return this._mutationQueue;
  }

  /**
   * Get the record store with only the cached and base data (no queued data).
   */
  getCachedStore(): RelayRecordStore {
    return this._cachedStore;
  }

  /**
   * Get the record store with full data (cached, base, queued).
   */
  getQueuedStore(): RelayRecordStore {
    return this._queuedStore;
  }

  /**
   * Get the record store with only the base data (no queued/cached data).
   */
  getRecordStore(): RelayRecordStore {
    return this._recordStore;
  }

  getQueryTracker(): RelayQueryTracker {
    return this._queryTracker;
  }

  getQueryRunner(): GraphQLQueryRunner {
    return this._queryRunner;
  }

  getChangeEmitter(): GraphQLStoreChangeEmitter {
    return this._changeEmitter;
  }

  getRangeData(): GraphQLStoreRangeUtils {
    return this._rangeData;
  }

  getPendingQueryTracker(): RelayPendingQueryTracker {
    return this._pendingQueryTracker;
  }

  /**
   * @deprecated
   *
   * Used temporarily by GraphQLStore, but all updates to this object are now
   * handled through a `RelayRecordStore` instance.
   */
  getRootCallData(): RootCallMap {
    return this._rootCallMap;
  }

  _isStoreDataEmpty(): boolean {
    return (
      Object.keys(this._records).length === 0 &&
      Object.keys(this._queuedRecords).length === 0 &&
      Object.keys(this._cachedRecords).length === 0
    );
  }

  /**
   * Given a ChangeSet, broadcasts changes for updated DataIDs
   * and registers new DataIDs with the garbage collector.
   */
  _handleChangedAndNewDataIDs(changeSet: ChangeSet): void {
    var updatedDataIDs = Object.keys(changeSet.updated);
    updatedDataIDs.forEach(id => this._changeEmitter.broadcastChangeForID(id));
    if (this._garbageCollector) {
      var createdDataIDs = Object.keys(changeSet.created);
      var garbageCollector = this._garbageCollector;
      createdDataIDs.forEach(dataID => garbageCollector.register(dataID));
    }
  }

  _getRecordStoreForMutation(): RelayRecordStore {
    var records = this._records;
    var rootCallMap = this._rootCallMap;

    return new RelayRecordStore(
      ({records}: $FixMe),
      ({rootCallMap}: $FixMe),
      (this._nodeRangeMap: $FixMe),
      this._cacheManager ?
        this._cacheManager.getMutationWriter() :
        null
    );
  }

  getRecordStoreForOptimisticMutation(
    clientMutationID: ClientMutationID
  ): RelayRecordStore {
    var cachedRecords = this._cachedRecords;
    var cachedRootCallMap = this._cachedRootCallMap;
    var rootCallMap = this._rootCallMap;
    var queuedRecords = this._queuedRecords;
    var records = this._records;

    return new RelayRecordStore(
      ({cachedRecords, queuedRecords, records}: $FixMe),
      ({cachedRootCallMap, rootCallMap}: $FixMe),
      (this._nodeRangeMap: $FixMe),
      null, // don't cache optimistic data
      clientMutationID
    );
  }
}

function createRecordCollection({
  cachedRecords,
  cachedRootCallMap,
  cacheWriter,
  queuedRecords,
  nodeRangeMap,
  records,
  rootCallMap,
}): {
  cachedStore: RelayRecordStore,
  queuedStore: RelayRecordStore,
  recordStore: RelayRecordStore
} {
  return {
    queuedStore: new RelayRecordStore(
      ({cachedRecords, queuedRecords, records}: $FixMe),
      ({cachedRootCallMap, rootCallMap}: $FixMe),
      (nodeRangeMap: $FixMe)
    ),
    cachedStore: new RelayRecordStore(
      ({cachedRecords, records}: $FixMe),
      ({cachedRootCallMap, rootCallMap}: $FixMe),
      (nodeRangeMap: $FixMe),
      cacheWriter
    ),
    recordStore: new RelayRecordStore(
      ({records}: $FixMe),
      ({rootCallMap}: $FixMe),
      (nodeRangeMap: $FixMe),
      cacheWriter
    ),
  };
}

RelayProfiler.instrumentMethods(RelayStoreData.prototype, {
  handleQueryPayload: 'RelayStoreData.prototype.handleQueryPayload',
  handleUpdatePayload: 'RelayStoreData.prototype.handleUpdatePayload',
});

module.exports = RelayStoreData;
<commit_msg>Canonicalize dataID before getting path<commit_after>/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RelayStoreData
 * @flow
 * @typechecks
 */

'use strict';

const GraphQLQueryRunner = require('GraphQLQueryRunner');
const GraphQLStoreChangeEmitter = require('GraphQLStoreChangeEmitter');
const GraphQLStoreDataHandler = require('GraphQLStoreDataHandler');
const GraphQLStoreRangeUtils = require('GraphQLStoreRangeUtils');
const RelayChangeTracker = require('RelayChangeTracker');
import type {ChangeSet} from 'RelayChangeTracker';
const RelayConnectionInterface = require('RelayConnectionInterface');
import type {GarbageCollectionScheduler} from 'RelayGarbageCollector';
const RelayGarbageCollector = require('RelayGarbageCollector');
const RelayMutationQueue = require('RelayMutationQueue');
import type {
  ClientMutationID,
  DataID,
  NodeRangeMap,
  Records,
  RelayQuerySet,
  RootCallMap,
  UpdateOptions,
} from 'RelayInternalTypes';
const RelayNodeInterface = require('RelayNodeInterface');
const RelayPendingQueryTracker = require('RelayPendingQueryTracker');
const RelayProfiler = require('RelayProfiler');
const RelayQuery = require('RelayQuery');
const RelayQueryTracker = require('RelayQueryTracker');
const RelayQueryWriter = require('RelayQueryWriter');
const RelayRecordStore = require('RelayRecordStore');
import type {CacheManager, CacheReadCallbacks} from 'RelayTypes';

const forEachObject = require('forEachObject');
const invariant = require('invariant');
const generateForceIndex = require('generateForceIndex');
const readRelayDiskCache = require('readRelayDiskCache');
const warning = require('warning');
const writeRelayQueryPayload = require('writeRelayQueryPayload');
const writeRelayUpdatePayload = require('writeRelayUpdatePayload');

var {CLIENT_MUTATION_ID} = RelayConnectionInterface;
var {NODE_TYPE} = RelayNodeInterface;

// The source of truth for application data.
var _instance;

/**
 * @internal
 *
 * Wraps the data caches and associated metadata tracking objects used by
 * GraphQLStore/RelayStore.
 */
class RelayStoreData {
  _cacheManager: ?CacheManager;
  _cachedRecords: Records;
  _cachedRootCallMap: RootCallMap;
  _cachedStore: RelayRecordStore;
  _changeEmitter: GraphQLStoreChangeEmitter;
  _garbageCollector: ?RelayGarbageCollector;
  _mutationQueue: RelayMutationQueue;
  _nodeRangeMap: NodeRangeMap;
  _pendingQueryTracker: RelayPendingQueryTracker;
  _records: Records;
  _queuedRecords: Records;
  _queuedStore: RelayRecordStore;
  _recordStore: RelayRecordStore;
  _queryTracker: RelayQueryTracker;
  _queryRunner: GraphQLQueryRunner;
  _rangeData: GraphQLStoreRangeUtils;
  _rootCallMap: RootCallMap;

  /**
   * Get the data set backing actual Relay operations. Used in GraphQLStore.
   */
  static getDefaultInstance(): RelayStoreData {
    if (!_instance) {
      _instance = new RelayStoreData();
    }
    return _instance;
  }

  constructor() {
    const cachedRecords: Records = ({}: $FixMe);
    const cachedRootCallMap: RootCallMap = {};
    const queuedRecords: Records = ({}: $FixMe);
    const records: Records = ({}: $FixMe);
    const rootCallMap: RootCallMap = {};
    const nodeRangeMap: NodeRangeMap = ({}: $FixMe);
    const {
      cachedStore,
      queuedStore,
      recordStore,
    } = createRecordCollection({
      cachedRecords,
      cachedRootCallMap,
      cacheWriter: null,
      queuedRecords,
      nodeRangeMap,
      records,
      rootCallMap,
    });
    const rangeData = new GraphQLStoreRangeUtils();

    this._cacheManager = null;
    this._cachedRecords = cachedRecords;
    this._cachedRootCallMap = cachedRootCallMap;
    this._cachedStore = cachedStore;
    this._changeEmitter = new GraphQLStoreChangeEmitter(rangeData);
    this._mutationQueue = new RelayMutationQueue(this);
    this._nodeRangeMap = nodeRangeMap;
    this._pendingQueryTracker = new RelayPendingQueryTracker(this);
    this._queryRunner = new GraphQLQueryRunner(this);
    this._queryTracker = new RelayQueryTracker();
    this._queuedRecords = queuedRecords;
    this._queuedStore = queuedStore;
    this._records = records;
    this._recordStore = recordStore;
    this._rangeData = rangeData;
    this._rootCallMap = rootCallMap;
  }

  /**
   * Creates a garbage collector for this instance. After initialization all
   * newly added DataIDs will be registered in the created garbage collector.
   * This will show a warning if data has already been added to the instance.
   */
  initializeGarbageCollector(scheduler: GarbageCollectionScheduler): void {
    invariant(
      !this._garbageCollector,
      'RelayStoreData: Garbage collector is already initialized.'
    );
    var shouldInitialize = this._isStoreDataEmpty();
    warning(
      shouldInitialize,
      'RelayStoreData: Garbage collection can only be initialized when no ' +
      'data is present.'
    );
    if (shouldInitialize) {
      this._garbageCollector = new RelayGarbageCollector(this, scheduler);
    }
  }

  /**
   * Sets/clears the cache manager that is used to cache changes written to
   * the store.
   */
  injectCacheManager(cacheManager: ?CacheManager): void {
    const {
      cachedStore,
      queuedStore,
      recordStore,
    } = createRecordCollection({
      cachedRecords: this._cachedRecords,
      cachedRootCallMap: this._cachedRootCallMap,
      cacheWriter: cacheManager ? cacheManager.getQueryWriter() : null,
      queuedRecords: this._queuedRecords,
      nodeRangeMap: this._nodeRangeMap,
      records: this._records,
      rootCallMap: this._rootCallMap,
    });

    this._cacheManager = cacheManager;
    this._cachedStore = cachedStore;
    this._queuedStore = queuedStore;
    this._recordStore = recordStore;
  }

  clearCacheManager(): void {
    const {
      cachedStore,
      queuedStore,
      recordStore,
    } = createRecordCollection({
      cachedRecords: this._cachedRecords,
      cachedRootCallMap: this._cachedRootCallMap,
      cacheWriter: null,
      queuedRecords: this._queuedRecords,
      nodeRangeMap: this._nodeRangeMap,
      records: this._records,
      rootCallMap: this._rootCallMap,
    });

    this._cacheManager = null;
    this._cachedStore = cachedStore;
    this._queuedStore = queuedStore;
    this._recordStore = recordStore;
  }

  hasCacheManager(): boolean {
    return !!this._cacheManager;
  }

  /**
   * Returns whether a given record is affected by an optimistic update.
   */
  hasOptimisticUpdate(dataID: DataID): boolean {
    dataID = this.getRangeData().getCanonicalClientID(dataID);
    return this.getQueuedStore().hasOptimisticUpdate(dataID);
  }

  /**
   * Returns a list of client mutation IDs for queued mutations whose optimistic
   * updates are affecting the record corresponding the given dataID. Returns
   * null if the record isn't affected by any optimistic updates.
   */
  getClientMutationIDs(dataID: DataID): ?Array<ClientMutationID> {
    dataID = this.getRangeData().getCanonicalClientID(dataID);
    return this.getQueuedStore().getClientMutationIDs(dataID);
  }

  /**
   * Reads data for queries incrementally from disk cache.
   * It calls onSuccess when all the data has been loaded into memory.
   * It calls onFailure when some data is unabled to be satisfied from disk.
   */
  readFromDiskCache(
    queries: RelayQuerySet,
    callbacks: CacheReadCallbacks
  ): void {
    var cacheManager = this._cacheManager;
    invariant(
      cacheManager,
      'RelayStoreData: `readFromDiskCache` should only be called when cache ' +
      'manager is available.'
    );
    var profile = RelayProfiler.profile('RelayStoreData.readFromDiskCache');
    readRelayDiskCache(
      queries,
      this._queuedStore,
      this._cachedRecords,
      this._cachedRootCallMap,
      cacheManager,
      {
        onSuccess: () => {
          profile.stop();
          callbacks.onSuccess && callbacks.onSuccess();
        },
        onFailure: () => {
          profile.stop();
          callbacks.onFailure && callbacks.onFailure();
        },
      }
    );
  }

  /**
   * Write the results of a query into the base record store.
   */
  handleQueryPayload(
    query: RelayQuery.Root,
    response: {[key: string]: mixed},
    forceIndex: ?number
  ): void {
    var profiler = RelayProfiler.profile('RelayStoreData.handleQueryPayload');
    var changeTracker = new RelayChangeTracker();
    var writer = new RelayQueryWriter(
      this._recordStore,
      this._queryTracker,
      changeTracker,
      {
        forceIndex,
        updateTrackedQueries: true,
      }
    );
    writeRelayQueryPayload(
      writer,
      query,
      response
    );
    this._handleChangedAndNewDataIDs(changeTracker.getChangeSet());
    profiler.stop();
  }

  /**
   * Write the results of an update into the base record store.
   */
  handleUpdatePayload(
    operation: RelayQuery.Operation,
    payload: {[key: string]: mixed},
    {configs, isOptimisticUpdate}: UpdateOptions
  ): void {
    var profiler = RelayProfiler.profile('RelayStoreData.handleUpdatePayload');
    var changeTracker = new RelayChangeTracker();
    var store;
    if (isOptimisticUpdate) {
      var clientMutationID = payload[CLIENT_MUTATION_ID];
      invariant(
        typeof clientMutationID === 'string',
        'RelayStoreData.handleUpdatePayload(): Expected optimistic payload ' +
        'to have a valid `%s`.',
        CLIENT_MUTATION_ID
      );
      store = this.getRecordStoreForOptimisticMutation(clientMutationID);
    } else {
      store = this._getRecordStoreForMutation();
    }
    var writer = new RelayQueryWriter(
      store,
      this._queryTracker,
      changeTracker,
      {
        forceIndex: generateForceIndex(),
        isOptimisticUpdate,
        updateTrackedQueries: false,
      }
    );
    writeRelayUpdatePayload(
      writer,
      operation,
      payload,
      {configs, isOptimisticUpdate}
    );
    this._handleChangedAndNewDataIDs(changeTracker.getChangeSet());
    profiler.stop();
  }

  /**
   * Given a query fragment and a data ID, returns a root query that applies
   * the fragment to the object specified by the data ID.
   */
  buildFragmentQueryForDataID(
    fragment: RelayQuery.Fragment,
    dataID: DataID
  ): RelayQuery.Root {
    if (GraphQLStoreDataHandler.isClientID(dataID)) {
      const path = this._queuedStore.getPathToRecord(
        this._rangeData.getCanonicalClientID(dataID),
      );
      invariant(
        path,
        'RelayStoreData.buildFragmentQueryForDataID(): Cannot refetch ' +
        'record `%s` without a path.',
        dataID
      );
      return path.getQuery(fragment);
    }
    // Fragment fields cannot be spread directly into the root because they
    // may not exist on the `Node` type.
    return RelayQuery.Root.build(
      fragment.getDebugName() || 'UnknownQuery',
      RelayNodeInterface.NODE,
      dataID,
      [fragment],
      {identifyingArgName: RelayNodeInterface.ID},
      NODE_TYPE
    );
  }

  getNodeData(): Records {
    return this._records;
  }

  getQueuedData(): Records {
    return this._queuedRecords;
  }

  clearQueuedData(): void {
    forEachObject(this._queuedRecords, (_, key) => {
      delete this._queuedRecords[key];
      this._changeEmitter.broadcastChangeForID(key);
    });
  }

  getCachedData(): Records {
    return this._cachedRecords;
  }

  getGarbageCollector(): ?RelayGarbageCollector {
    return this._garbageCollector;
  }

  getMutationQueue(): RelayMutationQueue {
    return this._mutationQueue;
  }

  /**
   * Get the record store with only the cached and base data (no queued data).
   */
  getCachedStore(): RelayRecordStore {
    return this._cachedStore;
  }

  /**
   * Get the record store with full data (cached, base, queued).
   */
  getQueuedStore(): RelayRecordStore {
    return this._queuedStore;
  }

  /**
   * Get the record store with only the base data (no queued/cached data).
   */
  getRecordStore(): RelayRecordStore {
    return this._recordStore;
  }

  getQueryTracker(): RelayQueryTracker {
    return this._queryTracker;
  }

  getQueryRunner(): GraphQLQueryRunner {
    return this._queryRunner;
  }

  getChangeEmitter(): GraphQLStoreChangeEmitter {
    return this._changeEmitter;
  }

  getRangeData(): GraphQLStoreRangeUtils {
    return this._rangeData;
  }

  getPendingQueryTracker(): RelayPendingQueryTracker {
    return this._pendingQueryTracker;
  }

  /**
   * @deprecated
   *
   * Used temporarily by GraphQLStore, but all updates to this object are now
   * handled through a `RelayRecordStore` instance.
   */
  getRootCallData(): RootCallMap {
    return this._rootCallMap;
  }

  _isStoreDataEmpty(): boolean {
    return (
      Object.keys(this._records).length === 0 &&
      Object.keys(this._queuedRecords).length === 0 &&
      Object.keys(this._cachedRecords).length === 0
    );
  }

  /**
   * Given a ChangeSet, broadcasts changes for updated DataIDs
   * and registers new DataIDs with the garbage collector.
   */
  _handleChangedAndNewDataIDs(changeSet: ChangeSet): void {
    var updatedDataIDs = Object.keys(changeSet.updated);
    updatedDataIDs.forEach(id => this._changeEmitter.broadcastChangeForID(id));
    if (this._garbageCollector) {
      var createdDataIDs = Object.keys(changeSet.created);
      var garbageCollector = this._garbageCollector;
      createdDataIDs.forEach(dataID => garbageCollector.register(dataID));
    }
  }

  _getRecordStoreForMutation(): RelayRecordStore {
    var records = this._records;
    var rootCallMap = this._rootCallMap;

    return new RelayRecordStore(
      ({records}: $FixMe),
      ({rootCallMap}: $FixMe),
      (this._nodeRangeMap: $FixMe),
      this._cacheManager ?
        this._cacheManager.getMutationWriter() :
        null
    );
  }

  getRecordStoreForOptimisticMutation(
    clientMutationID: ClientMutationID
  ): RelayRecordStore {
    var cachedRecords = this._cachedRecords;
    var cachedRootCallMap = this._cachedRootCallMap;
    var rootCallMap = this._rootCallMap;
    var queuedRecords = this._queuedRecords;
    var records = this._records;

    return new RelayRecordStore(
      ({cachedRecords, queuedRecords, records}: $FixMe),
      ({cachedRootCallMap, rootCallMap}: $FixMe),
      (this._nodeRangeMap: $FixMe),
      null, // don't cache optimistic data
      clientMutationID
    );
  }
}

function createRecordCollection({
  cachedRecords,
  cachedRootCallMap,
  cacheWriter,
  queuedRecords,
  nodeRangeMap,
  records,
  rootCallMap,
}): {
  cachedStore: RelayRecordStore,
  queuedStore: RelayRecordStore,
  recordStore: RelayRecordStore
} {
  return {
    queuedStore: new RelayRecordStore(
      ({cachedRecords, queuedRecords, records}: $FixMe),
      ({cachedRootCallMap, rootCallMap}: $FixMe),
      (nodeRangeMap: $FixMe)
    ),
    cachedStore: new RelayRecordStore(
      ({cachedRecords, records}: $FixMe),
      ({cachedRootCallMap, rootCallMap}: $FixMe),
      (nodeRangeMap: $FixMe),
      cacheWriter
    ),
    recordStore: new RelayRecordStore(
      ({records}: $FixMe),
      ({rootCallMap}: $FixMe),
      (nodeRangeMap: $FixMe),
      cacheWriter
    ),
  };
}

RelayProfiler.instrumentMethods(RelayStoreData.prototype, {
  handleQueryPayload: 'RelayStoreData.prototype.handleQueryPayload',
  handleUpdatePayload: 'RelayStoreData.prototype.handleUpdatePayload',
});

module.exports = RelayStoreData;
<|endoftext|>


<commit_before>import { createStore } from 'redux';
import rootReducer from '../reducers';

export default (initialState) => {
  const store = createStore(rootReducer, initialState);

  if (module.hot) {
    module.hot.accept('../reducers', () => {
      /* eslint-disable global-require */
      const nextRootReducer = require('../reducers').default;
      /* eslint-enable global-require */
      store.replaceReducer(nextRootReducer);
    });
  }

  return store;
};
<commit_msg>add redux-devtools extension support<commit_after>import { createStore } from 'redux';
import rootReducer from '../reducers';

export default (initialState) => {
  /* eslint-disable no-underscore-dangle */
  const store = createStore(
    rootReducer,
    initialState,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
  );
  /* eslint-enable */

  if (module.hot) {
    module.hot.accept('../reducers', () => {
      /* eslint-disable global-require */
      const nextRootReducer = require('../reducers').default;
      /* eslint-enable global-require */
      store.replaceReducer(nextRootReducer);
    });
  }

  return store;
};
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var React = require('react/addons');
var renderTarget, component;

xdescribe('displaying article overview', () => {
  var categories = [{id:1}, {id:2}];
  var articles = [{id:3, intensity: 3}, {id:4, intensity: 8}];

  beforeEach(() => {
    var ComponentClass = require('../../app/components/app.react.js');
    renderTarget = document.getElementsByTagName('body')[0];

    var dataAccess = {
      getCategoriesAndArticles: () => {
        return {
          categories: categories,
          articles: articles
        };
      }
    };

    var ActionCreator = require('../../app/action-creator.js');
    var actionCreator = new ActionCreator(dataAccess);
    var ArticleStore = require('../../app/article-store.js');
    var store = new ArticleStore();
    var renderedComponent = React.render(<ComponentClass actionCreator={actionCreator} store={store}/>, renderTarget);
    component = renderedComponent;
  });

  afterEach(() => {
    React.unmountComponentAtNode(renderTarget);
    component = null;
  });

  describe('server has some categories and articles', () => {
    it('should display categories from server', () => {
      expect(component.state.categories).to.deep.equal(categories);
    });

    it('should display articles from server', () => {
      var expected = articles.map((article) => {
        article.isMatchingFilter = true;
        return article;
      });
      expect(component.state.articles).to.deep.equal(expected);
    });
  });
});
<commit_msg>Refactor to e2e-testing instead of state comparison.<commit_after>var expect = require('chai').expect;
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var renderTarget, renderedComponent;

describe('displaying article overview', () => {
  var categories = [{id:1, name:'first category'}, {id:2, name:'second category'}];
  var articles = [{id:3, intensity: 3}, {id:4, intensity: 8}];

  beforeEach(() => {
    var ComponentClass = require('../../app/components/app.react.js');
    renderTarget = document.getElementsByTagName('body')[0];

    var dataAccess = {
      getCategoriesAndArticles: () => {
        return {
          categories: categories,
          articles: articles
        };
      }
    };

    var ActionCreator = require('../../app/action-creator.js');
    var actionCreator = new ActionCreator(dataAccess);
    var ArticleStore = require('../../app/article-store.js');
    var store = new ArticleStore();
    renderedComponent = React.render(<ComponentClass actionCreator={actionCreator} store={store}/>, renderTarget);
  });

  afterEach(() => {
    React.unmountComponentAtNode(renderTarget);
    renderedComponent = null;
  });

  describe('server has some categories and articles', () => {
    it('should display categories from server', () => {
      let actualCategoryNames = TestUtils.scryRenderedDOMComponentsWithClass(renderedComponent, 'category').map((category) => category.getDOMNode().textContent);
      let expectedCategoryNames = categories.map((category) => category.name);
      expect(actualCategoryNames).to.deep.equal(expectedCategoryNames);
    });

    // it('should display articles from server', () => {
    //   var expected = articles.map((article) => {
    //     article.isMatchingFilter = true;
    //     return article;
    //   });
    //   expect(component.state.articles).to.deep.equal(expected);
    // });
  });
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
   package: 'foam.nanos.auth',
   name: 'User',

   implements: [
     'foam.nanos.auth.EnabledAware',
     'foam.nanos.auth.LastModifiedAware',
     'foam.nanos.auth.LastModifiedByAware'
   ],

   documentation: '',

   tableColumns: [
     'id', 'enabled', 'firstName', 'lastName', 'organization', 'lastModified'
   ],

   properties: [
     {
       class: 'Long',
       name: 'id',
       displayWidth: 30,
       width: 100
     },
     {
       class: 'String',
       // class: 'SPID',
       label: 'Service Provider',
       name: 'spid',
       documentation: "User's service provider."
     },
     {
       class: 'DateTime',
       name: 'lastLogin'
     },
     {
       class: 'String',
       name: 'firstName'
     },
     {
       class: 'String',
       name: 'middleName'
     },
     {
       class: 'String',
       name: 'lastName'
     },
     {
       class: 'String',
       name: 'organization'
     },
     {
       class: 'String',
       name: 'department'
     },
     {
       class: 'String',
       // class: 'Email',
       name: 'email'
     },
     {
       class: 'String',
       // class: 'Phone',
       name: 'phone'
     },
     {
       class: 'String',
       // class: 'Phone',
       name: 'mobile'
     },
     {
       class: 'String',
       name: 'type'
     },
     {
       class: 'Date',
       name:  'birthday'
     },
     {
       class: 'String',
       name:  'profilePicture'
     },
     {
       class: 'Reference',
       name: 'language',
       of: 'foam.nanos.auth.Language',
       value: 'en'
     },
     {
       class: 'String',
       name: 'timeZone'
       // TODO: create custom view or DAO
     },
     {
       class: 'Password',
       name: 'password',
       displayWidth: 30,
       width: 100
     },
     {
       class: 'Password',
       name: 'previousPassword',
       displayWidth: 30,
       width: 100
     },
     {
       class: 'DateTime',
       name: 'passwordLastModified'
     },
     // TODO: startDate, endDate,
     // TODO: do we want to replace 'note' with a simple ticket system?
     {
       class: 'String',
       name: 'note',
       displayWidth: 70,
       displayHeight: 10
     }
   ]
 });
<commit_msg>fixed spacing<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
   package: 'foam.nanos.auth',
   name: 'User',

   implements: [
     'foam.nanos.auth.EnabledAware',
     'foam.nanos.auth.LastModifiedAware',
     'foam.nanos.auth.LastModifiedByAware'
   ],

   documentation: '',

   tableColumns: [
     'id', 'enabled', 'firstName', 'lastName', 'organization', 'lastModified'
   ],

   properties: [
     {
       class: 'Long',
       name: 'id',
       displayWidth: 30,
       width: 100
     },
     {
       class: 'String',
       // class: 'SPID',
       label: 'Service Provider',
       name: 'spid',
       documentation: "User's service provider."
     },
     {
       class: 'DateTime',
       name: 'lastLogin'
     },
     {
       class: 'String',
       name: 'firstName'
     },
     {
       class: 'String',
       name: 'middleName'
     },
     {
       class: 'String',
       name: 'lastName'
     },
     {
       class: 'String',
       name: 'organization'
     },
     {
       class: 'String',
       name: 'department'
     },
     {
       class: 'String',
       // class: 'Email',
       name: 'email'
     },
     {
       class: 'String',
       // class: 'Phone',
       name: 'phone'
     },
     {
       class: 'String',
       // class: 'Phone',
       name: 'mobile'
     },
     {
       class: 'String',
       name: 'type'
     },
     {
       class: 'Date',
       name: 'birthday'
     },
     {
       class: 'String',
       name: 'profilePicture'
     },
     {
       class: 'Reference',
       name: 'language',
       of: 'foam.nanos.auth.Language',
       value: 'en'
     },
     {
       class: 'String',
       name: 'timeZone'
       // TODO: create custom view or DAO
     },
     {
       class: 'Password',
       name: 'password',
       displayWidth: 30,
       width: 100
     },
     {
       class: 'Password',
       name: 'previousPassword',
       displayWidth: 30,
       width: 100
     },
     {
       class: 'DateTime',
       name: 'passwordLastModified'
     },
     // TODO: startDate, endDate,
     // TODO: do we want to replace 'note' with a simple ticket system?
     {
       class: 'String',
       name: 'note',
       displayWidth: 70,
       displayHeight: 10
     }
   ]
 });
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'CitationView',
  extends: 'foam.u2.View',
  axioms: [
    foam.pattern.Faceted.create()
  ],
  properties: [
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'String',
      name: 'summary'
    }
  ],
  reactions: [
    ['', 'propertyChange.data', 'updateSummary'],
    ['data', 'propertyChange', 'updateSummary']
  ],
  listeners: [
    {
      name: 'updateSummary',
      isFramed: true,
      code: function() {
        this.summary = this.data ? this.data.toSummary() : undefined;
      }
    }
  ],
  methods: [
   function initE() {
      this.SUPER();
      this.add(this.summary$);
    }
  ]
});
<commit_msg>Formatting.<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'CitationView',
  extends: 'foam.u2.View',

  axioms: [
    foam.pattern.Faceted.create()
  ],

  properties: [
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'String',
      name: 'summary'
    }
  ],

  reactions: [
    ['', 'propertyChange.data', 'updateSummary'],
    ['data', 'propertyChange', 'updateSummary']
  ],

  listeners: [
    {
      name: 'updateSummary',
      isFramed: true,
      code: function() {
        this.summary = this.data ? this.data.toSummary() : undefined;
      }
    }
  ],

  methods: [
   function initE() {
      this.SUPER();
      this.add(this.summary$);
    }
  ]
});
<|endoftext|>


<commit_before>import Vue from 'vue'
import stores from '@/services/api/stores'
import { indexById } from '@/store/helpers'
import router from '@/router'

export const types = {

  SELECT_STORE: 'Select Store',
  CLEAR_SELECTED_STORE: 'Clear Selected Store',

  REQUEST_STORES: 'Request Stores',
  RECEIVE_STORES: 'Receive Stores',
  RECEIVE_STORES_ERROR: 'Receive Stores Error',

  REQUEST_SAVE: 'Request Save',
  RECEIVE_SAVE: 'Receive Save',
  RECEIVE_SAVE_ERROR: 'Receive Save Error',

  RECEIVE_ITEM: 'Receive Item',

  CLEAR: 'Clear',
}

export const state = {
  entries: {},
  idList: [],
  isFetching: false,
  error: null,
  activeStoreId: null,
}

export const getters = {
  all: state => state.idList.map(i => state.entries[i]),
  get: state => (id) => state.entries[id],
  withLocation: (state, getters) => getters.all.filter(e => e.longitude && e.latitude),
  activeStore: state => state.entries[state.activeStoreId] || {},
  activeStoreId: state => state.activeStoreId,
  status: state => { return { isFetching: state.isFetching, error: state.error } },
}

export const actions = {
  async selectStore ({ commit, state, dispatch, getters, rootState }, storeId) {
    dispatch('pickups/setStoreFilter', storeId, {root: true})
    commit(types.SELECT_STORE, { storeId })
  },

  async clearSelectedStore ({ commit, dispatch }) {
    dispatch('pickups/clearStoreFilter', null, { root: true })
    commit(types.CLEAR_SELECTED_STORE)
  },

  async fetchListByGroupId ({ commit }, groupId) {
    commit(types.REQUEST_STORES)
    try {
      commit(types.RECEIVE_STORES, { stores: await stores.listByGroupId(groupId) })
    }
    catch (error) {
      commit(types.RECEIVE_STORES_ERROR, { error })
    }
  },

  async save ({ commit, dispatch }, store) {
    const updatedStore = await stores.save(store)
    commit(types.RECEIVE_ITEM, { store: updatedStore })
  },

  async create ({ commit, dispatch, rootGetters }, store) {
    commit(types.REQUEST_SAVE)
    let createdStore
    try {
      createdStore = await stores.create({
        ...store,
        group: rootGetters['groups/activeGroupId'],
      })
    }
    catch (error) {
      commit(types.RECEIVE_SAVE_ERROR, { error })
      return
    }
    commit(types.RECEIVE_SAVE)
    commit(types.RECEIVE_ITEM, { store: createdStore })
    router.push({ name: 'store', params: { storeId: createdStore.id } })
  },

  clear ({ commit, dispatch }) {
    commit(types.CLEAR)
  },
}

export const mutations = {
  [types.SELECT_STORE] (state, { storeId }) {
    state.activeStoreId = storeId
  },
  [types.CLEAR_SELECTED_STORE] (state) {
    state.activeStoreId = null
  },
  [types.REQUEST_STORES] (state) {
    state.isFetching = true
  },
  [types.RECEIVE_STORES] (state, { stores }) {
    state.isFetching = false
    state.entries = indexById(stores)
    state.idList = stores.map(e => e.id)
  },
  [types.RECEIVE_STORES_ERROR] (state, { error }) {
    state.isFetching = false
    state.error = error
  },
  [types.CLEAR] (state) {
    state.activeStoreId = null
    state.entries = {}
    state.idList = []
  },
  [types.REQUEST_SAVE] (state) {
    state.isFetching = true
    state.error = null
  },
  [types.RECEIVE_SAVE] (state) {
    state.isFetching = false
    state.error = null
  },
  [types.RECEIVE_SAVE_ERROR] (state, { error }) {
    state.isFetching = false
    state.error = error
  },
  [types.RECEIVE_ITEM] (state, { store }) {
    Vue.set(state.entries, store.id, store)
    if (!state.idList.includes(store.id)) {
      state.idList.push(store.id)
    }
    // TODO sort by name
  },
}
<commit_msg>state management for store save<commit_after>import Vue from 'vue'
import stores from '@/services/api/stores'
import { indexById } from '@/store/helpers'
import router from '@/router'

export const types = {

  SELECT_STORE: 'Select Store',
  CLEAR_SELECTED_STORE: 'Clear Selected Store',

  REQUEST_STORES: 'Request Stores',
  RECEIVE_STORES: 'Receive Stores',
  RECEIVE_STORES_ERROR: 'Receive Stores Error',

  REQUEST_SAVE: 'Request Save',
  RECEIVE_SAVE: 'Receive Save',
  RECEIVE_SAVE_ERROR: 'Receive Save Error',

  RECEIVE_ITEM: 'Receive Item',

  CLEAR: 'Clear',
}

export const state = {
  entries: {},
  idList: [],
  isFetching: false,
  error: null,
  activeStoreId: null,
}

export const getters = {
  all: state => state.idList.map(i => state.entries[i]),
  get: state => (id) => state.entries[id],
  withLocation: (state, getters) => getters.all.filter(e => e.longitude && e.latitude),
  activeStore: state => state.entries[state.activeStoreId] || {},
  activeStoreId: state => state.activeStoreId,
  status: state => { return { isFetching: state.isFetching, error: state.error } },
}

export const actions = {
  async selectStore ({ commit, state, dispatch, getters, rootState }, storeId) {
    dispatch('pickups/setStoreFilter', storeId, {root: true})
    commit(types.SELECT_STORE, { storeId })
  },

  async clearSelectedStore ({ commit, dispatch }) {
    dispatch('pickups/clearStoreFilter', null, { root: true })
    commit(types.CLEAR_SELECTED_STORE)
  },

  async fetchListByGroupId ({ commit }, groupId) {
    commit(types.REQUEST_STORES)
    try {
      commit(types.RECEIVE_STORES, { stores: await stores.listByGroupId(groupId) })
    }
    catch (error) {
      commit(types.RECEIVE_STORES_ERROR, { error })
    }
  },

  async save ({ commit, dispatch }, store) {
    commit(types.REQUEST_SAVE)
    let updatedStore
    try {
      updatedStore = await stores.save(store)
    }
    catch (error) {
      commit(types.RECEIVE_SAVE_ERROR, { error })
      return
    }
    commit(types.RECEIVE_SAVE)
    commit(types.RECEIVE_ITEM, { store: updatedStore })
    router.push({ name: 'store', params: { storeId: updatedStore.id } })
  },

  async create ({ commit, dispatch, rootGetters }, store) {
    commit(types.REQUEST_SAVE)
    let createdStore
    try {
      createdStore = await stores.create({
        ...store,
        group: rootGetters['groups/activeGroupId'],
      })
    }
    catch (error) {
      commit(types.RECEIVE_SAVE_ERROR, { error })
      return
    }
    commit(types.RECEIVE_SAVE)
    commit(types.RECEIVE_ITEM, { store: createdStore })
    router.push({ name: 'store', params: { storeId: createdStore.id } })
  },

  clear ({ commit, dispatch }) {
    commit(types.CLEAR)
  },
}

export const mutations = {
  [types.SELECT_STORE] (state, { storeId }) {
    state.activeStoreId = storeId
  },
  [types.CLEAR_SELECTED_STORE] (state) {
    state.activeStoreId = null
  },
  [types.REQUEST_STORES] (state) {
    state.isFetching = true
  },
  [types.RECEIVE_STORES] (state, { stores }) {
    state.isFetching = false
    state.entries = indexById(stores)
    state.idList = stores.map(e => e.id)
  },
  [types.RECEIVE_STORES_ERROR] (state, { error }) {
    state.isFetching = false
    state.error = error
  },
  [types.CLEAR] (state) {
    state.activeStoreId = null
    state.entries = {}
    state.idList = []
  },
  [types.REQUEST_SAVE] (state) {
    state.isFetching = true
    state.error = null
  },
  [types.RECEIVE_SAVE] (state) {
    state.isFetching = false
    state.error = null
  },
  [types.RECEIVE_SAVE_ERROR] (state, { error }) {
    state.isFetching = false
    state.error = error
  },
  [types.RECEIVE_ITEM] (state, { store }) {
    Vue.set(state.entries, store.id, store)
    if (!state.idList.includes(store.id)) {
      state.idList.push(store.id)
    }
    // TODO sort by name
  },
}
<|endoftext|>


<commit_before>var db = require('../aDbDriver.js')();
var mongoose = require('mongoose');
var path = require('path');

mongoose.connection.on('error', function (err) {
    console.log('[MongoDB Driver] Error while trying to connect');
});

db._models = [];
db._modelPath = '/models/entities/';

db.connect = function (host, port, database, user, password) {
    if (!mongoose.connection.readyState) {
        mongoose.connect('mongodb://' + user + ':' + password + '@' + host + ':' + port + '/' + database);
    }
};

db.disconnect = function () {
    if (mongoose.connection.readyState) {
        mongoose.disconnect();
    }
};

db.getEntity = function (name) {
    var entity = name;

    var splitted = process.mainModule.filename.split(path.sep);
    splitted.pop();
    var requirePath = splitted.join(path.sep);

    try {
        entity = require(path.normalize(requirePath + this._modelPath + entity.capitalize()));
    }
    catch (err) {
        return {
            success: false,
            error: {
                msg: 'error parsing entity',
                text: err
            }
        };
    }

    return {
        success: true,
        data: entity
    };
};

db.getModel = function (name) {
    var entity = this.getEntity(name);

    if (!entity.success) {
        return;
    }

    entity = entity.data;

    if (!this._models[entity.getCollection()]) {
        this._models[entity.getCollection()] = mongoose.model(
            entity.getCollection(),
            entity.getSchema(),
            entity.getCollection()
        );
    }

    return this._models[entity.getCollection()];
};

db.create = function (data, callback) {
    if (typeof data !== 'object') {
        console.log('[MongoDB Driver] Error: data has to be an object');
        return;
    }

    var model = this.getModel(entity);
    model.create(data, function (err) {
        if (typeof callback === 'function') {
            callback(false, err);
        }
    });
};

db.read = function (data, callback) {
    if (typeof data !== 'object') {
        callback(false, {
            msg: 'data is not an object'
        });
    }

    var model = this.getModel(data.entity);
    var query;

    if (data.recursive) {
        var dependents = this.loadDependentModels(model.schema);
    }

    if (data.key === '_id') {
        if (mongoose.Types.ObjectId.isValid(data.value)) {
            query = model.findById(data.value);
        }
    }
    else {
        var match = this.buildMatchObject(data);
        query = model.find(match, data.select, data.modify);
    }

    if (query) {
        if (data.recursive && dependents.length) {
            query = query.populate(dependents.join(' '));
        }

        query.exec(function (err, data) {
            if (err) {
                callback(false, err);
            }
            else {
                if (data.length) {
                    data = [data];
                }
                callback(true, data);
            }
        });
    }
    else {
        callback(false, {});
    }
};

db.update = function (data, callback) {
    if (typeof data !== 'object') {
        console.log('[MongoDB Driver] Error: data has to be an object');
        return;
    }

    var model = this.getModel(data.entity);

    model.update(data.match, data.update, data.options, function (err, data) {
        if (err) {
            console.log('[MongoDB Driver] Error updating data');
        }
        else {
            callback(true, data);
        }
    });
};

db.buildMatchObject = function (data) {
    if (data.key && data.value) {
        var match = {};
        var model = this.getModel(data.entity);
        var schemaType = model.schema.path(data.key);

        switch (schemaType.instance) {
            case 'ObjectID':
                if (mongoose.Types.ObjectId.isValid(data.value)) {
                    match[data.key] = new mongoose.Types.ObjectId(data.value);
                }
                else {
                    match[data.key] = null;
                }
                break;

            case 'Number':
                match[data.key] = data.value;
                break;

            case 'String':
                var regex = new RegExp('^' + data.value + '$', 'i');
                match[data.key] = regex;
                break;

            default:
                var msg = 'type "' + schemaType.instance + '" is not implemented yet';
                console.log(msg);
                return false;
        }

        return match;
    }

    return null;
};

db.loadDependentModels = function (schema) {
    var dependents = [];
    if (schema.hasOwnProperty('tree')) {
        for (var item in schema.tree) {
            if (schema.tree[item].hasOwnProperty('ref') ||
                (schema.tree[item][0]) && schema.tree[item][0].hasOwnProperty('ref')
            ) {
                if (schema.tree[item][0]) {
                    this.getModel(schema.tree[item][0].ref);
                }
                else {
                    this.getModel(schema.tree[item].ref);
                }
                dependents.push(item);
            }
        }

        return dependents;
    }

    return false;
};

module.exports = db;
<commit_msg>- structural changes in api controller and db driver - implemented recursive calls with mongoose populate - deleted entity calls in controller<commit_after>var db = require('../aDbDriver.js')();
var mongoose = require('mongoose');
var path = require('path');

mongoose.connection.on('error', function (err) {
    console.log('[MongoDB Driver] Error while trying to connect');
});

db._models = [];
db._modelPath = '/models/entities/';

db.connect = function (host, port, database, user, password) {
    if (!mongoose.connection.readyState) {
        mongoose.connect('mongodb://<' + user + ':' + password + '@' + host + ':' + port + '/' + database);
    }
};

db.disconnect = function () {
    if (mongoose.connection.readyState) {
        mongoose.disconnect();
    }
};

db.getEntity = function (name) {
    var entity = name;

    var splitted = process.mainModule.filename.split(path.sep);
    splitted.pop();
    var requirePath = splitted.join(path.sep);

    try {
        entity = require(path.normalize(requirePath + this._modelPath + entity.capitalize()));
    }
    catch (err) {
        return {
            success: false,
            error: {
                msg: 'error parsing entity',
                text: err
            }
        };
    }

    return {
        success: true,
        data: entity
    };
};

db.getModel = function (name) {
    var entity = this.getEntity(name);

    if (!entity.success) {
        return;
    }

    entity = entity.data;

    if (!this._models[entity.getCollection()]) {
        this._models[entity.getCollection()] = mongoose.model(
            entity.getCollection(),
            entity.getSchema(),
            entity.getCollection()
        );
    }

    return this._models[entity.getCollection()];
};

db.create = function (data, callback) {
    if (typeof data !== 'object') {
        console.log('[MongoDB Driver] Error: data has to be an object');
        return;
    }

    var model = this.getModel(entity);
    model.create(data, function (err) {
        if (typeof callback === 'function') {
            callback(false, err);
        }
    });
};

db.read = function (data, callback) {
    if (typeof data !== 'object') {
        callback(false, {
            msg: 'data is not an object'
        });
    }

    var model = this.getModel(data.entity);
    var query;

    if (data.recursive) {
        var dependents = this.loadDependentModels(model.schema);
    }

    if (data.key === '_id') {
        if (mongoose.Types.ObjectId.isValid(data.value)) {
            query = model.findById(data.value);
        }
    }
    else {
        var match = this.buildMatchObject(data);
        query = model.find(match, data.select, data.modify);
    }

    if (query) {
        if (data.recursive && dependents.length) {
            query = query.populate(dependents.join(' '));
        }

        query.exec(function (err, data) {
            if (err) {
                callback(false, err);
            }
            else {
                if (data.length) {
                    data = [data];
                }
                callback(true, data);
            }
        });
    }
    else {
        callback(false, {});
    }
};

db.update = function (data, callback) {
    if (typeof data !== 'object') {
        console.log('[MongoDB Driver] Error: data has to be an object');
        return;
    }

    var model = this.getModel(data.entity);

    model.update(data.match, data.update, data.options, function (err, data) {
        if (err) {
            console.log('[MongoDB Driver] Error updating data');
        }
        else {
            callback(true, data);
        }
    });
};

db.buildMatchObject = function (data) {
    if (data.key && data.value) {
        var match = {};
        var model = this.getModel(data.entity);
        var schemaType = model.schema.path(data.key);

        switch (schemaType.instance) {
            case 'ObjectID':
                if (mongoose.Types.ObjectId.isValid(data.value)) {
                    match[data.key] = new mongoose.Types.ObjectId(data.value);
                }
                else {
                    match[data.key] = null;
                }
                break;

            case 'Number':
                match[data.key] = data.value;
                break;

            case 'String':
                var regex = new RegExp('^' + data.value + '$', 'i');
                match[data.key] = regex;
                break;

            default:
                var msg = 'type "' + schemaType.instance + '" is not implemented yet';
                console.log(msg);
                return false;
        }

        return match;
    }

    return null;
};

db.loadDependentModels = function (schema) {
    var dependents = [];
    if (schema.hasOwnProperty('tree')) {
        for (var item in schema.tree) {
            if (schema.tree[item].hasOwnProperty('ref') ||
                (schema.tree[item][0]) && schema.tree[item][0].hasOwnProperty('ref')
            ) {
                if (schema.tree[item][0]) {
                    this.getModel(schema.tree[item][0].ref);
                }
                else {
                    this.getModel(schema.tree[item].ref);
                }
                dependents.push(item);
            }
        }

        return dependents;
    }

    return false;
};

module.exports = db;
<|endoftext|>