UNPKG

ember-data-factory-guy

Version:
2,614 lines 79.5 kB
import Store from '@ember-data/store';
import { assert } from '@ember/debug';
import { typeOf, isEmpty, isPresent } from '@ember/utils';
import { A } from '@ember/array';
import { macroCondition, dependencySatisfies, importSync, getOwnConfig } from '@embroider/macros';
import JSONSerializer from '@ember-data/serializer/json';
import RESTSerializer from '@ember-data/serializer/rest';
import JSONAPISerializer from '@ember-data/serializer/json-api';
import { camelize, dasherize, w, underscore } from '@ember/string';
import { getOwner } from '@ember/application';
import { pluralize } from 'ember-inflector';
import { isEqual, isMatch } from 'lodash';

/**
 Base class for converting the base fixture that factory guy creates to
 the payload expected by ember data adapter.

 While it's converting the format, it transforms the attribute and relationship keys.
 Don't want to transform keys when building payload for a FactoryGuy#make/makeList operation,
 but only for build/buildList.

 serializerMode (true) means => produce json as if ember data was serializing the payload
 to go back to the server.
 So, what does serializerMode ( false ) mean? produce json that can immediately be consumed
 by ember data .. but it is such a long story .. that I will have to explain another time

 If there are associations in the base fixture, they will be added to the
 new fixture as 'side loaded' elements, even if they are another json payload
 built with the build/buildList methods.

 @param {DS.Store} store
 @param {Object} options
 transformKeys transform keys and values in fixture if true
 serializeMode act like serialization is for a return to server if true
 @constructor
 */
class FixtureConverter {
  constructor(store, {
    transformKeys = true,
    serializeMode = false
  } = {}) {
    this.transformKeys = transformKeys;
    this.serializeMode = serializeMode;
    this.store = store;
    this.listType = false;
    this.noTransformFn = x => x;
    this.defaultValueTransformFn = this.noTransformFn;
  }

  /**
   Convert an initial fixture into a final payload.
   This raw fixture can contain other json in relationships that were
   built by FactoryGuy ( build, buildList ) methods
    @param modelName
   @param fixture
   @returns {*} converted fixture
   */
  convert(modelName, fixture) {
    let data;
    if (typeOf(fixture) === 'array') {
      this.listType = true;
      data = fixture.map(single => {
        return this.convertSingle(modelName, single);
      });
    } else {
      data = this.convertSingle(modelName, fixture);
    }
    let payload = this.createPayload(modelName, data);
    this.addIncludedArray(payload);
    return payload;
  }

  /**
   Empty response is a special case, so use this method for generating it.
    @param _
   @param {Object} options useValue to override the null value that is passed
   @returns {Array|null}
   */
  emptyResponse(_, options = {}) {
    return options.useValue || null;
  }

  /**
   * Use the primaryKey from the serializer if it is declared
   *
   * @param modelName
   * @param data
   * @param fixture
   */
  addPrimaryKey(modelName, data, fixture) {
    let primaryKey = this.store.serializerFor(modelName).get('primaryKey'),
      primaryKeyValue = fixture[primaryKey] || fixture.id;
    // model fragments will have no primaryKey and don't want them to have id
    if (primaryKeyValue) {
      // need to set the id for all as a baseline
      data.id = primaryKeyValue;
      // if the id is NOT the primary key, need to make sure that the primaryKey
      // has the primaryKey value
      data[primaryKey] = primaryKeyValue;
    }
  }
  transformRelationshipKey(relationship, parentModelName) {
    const type = /* parentModelName and parentType are not present on relationship schema in ember-data 5+, but these are here for backwards compat */
      relationship.parentModelName || relationship.parentType?.modelName || /**/
      parentModelName || relationship.type,
      transformFn = this.getTransformKeyFunction(type, 'Relationship');
    return transformFn(relationship.name, relationship.kind);
  }
  getRelationshipType(relationship, fixture) {
    let isPolymorphic = relationship.options.polymorphic;
    return isPolymorphic ? this.polymorphicTypeTransformFn(fixture.type) : relationship.type;
  }
  attributeIncluded(attribute, modelName) {
    if (!this.serializeMode) {
      return true;
    }
    let serializer = this.store.serializerFor(modelName),
      attrOptions = this.attrsOption(serializer, attribute);
    if (attrOptions && attrOptions.serialize === false) {
      return false;
    }
    return true;
  }
  getTransformKeyFunction(modelName, type) {
    if (!this.transformKeys) {
      return this.noTransformFn;
    }
    let serializer = this.store.serializerFor(modelName),
      keyFn = serializer['keyFor' + type] || this.defaultKeyTransformFn;
    return (attribute, method) => {
      // if there is an attrs override in serializer, return that first
      let attrOptions = this.attrsOption(serializer, attribute),
        attrName;
      if (attrOptions) {
        if (attrOptions.key) {
          attrName = attrOptions.key;
        }
        if (typeOf(attrOptions) === 'string') {
          attrName = attrOptions;
        }
      }
      return attrName || keyFn.apply(serializer, [attribute, method, 'serialize']);
    };
  }
  getTransformValueFunction(type) {
    if (!this.transformKeys) {
      return this.noTransformFn;
    }
    if (!type) {
      return this.defaultValueTransformFn;
    }
    let container = getOwner(this.store),
      transform = container.lookup('transform:' + type);
    assert(`[ember-data-factory-guy] could not find
    the [ ${type} ] transform. If you are in a unit test, be sure
    to include it in the list of needs as [ transform:${type} ],  Or set your
    unit test to be [ integration: true ] and include everything.`, transform);
    let transformer = container.lookup('transform:' + type);
    return transformer.serialize.bind(transformer);
  }
  extractAttributes(modelName, fixture) {
    let attributes = {},
      transformKeyFunction = this.getTransformKeyFunction(modelName, 'Attribute');
    this.store.modelFor(modelName).eachAttribute((attribute, meta) => {
      if (this.attributeIncluded(attribute, modelName)) {
        let attributeKey = transformKeyFunction(attribute),
          transformValueFunction = this.getTransformValueFunction(meta.type);
        if (Object.prototype.hasOwnProperty.call(fixture, attribute)) {
          attributes[attributeKey] = transformValueFunction(fixture[attribute], meta.options);
        } else if (Object.prototype.hasOwnProperty.call(fixture, attributeKey)) {
          attributes[attributeKey] = transformValueFunction(fixture[attributeKey], meta.options);
        }
      }
    });
    return attributes;
  }

  /**
   Extract relationships and descend into those looking for others
    @param {String} modelName
   @param {Object} fixture
   @returns {{}}
   */
  extractRelationships(modelName, fixture) {
    let relationships = {};
    this.store.modelFor(modelName).eachRelationship((key, relationship) => {
      if (Object.prototype.hasOwnProperty.call(fixture, key)) {
        if (relationship.kind === 'belongsTo') {
          this.extractBelongsTo(fixture, relationship, modelName, relationships);
        } else if (relationship.kind === 'hasMany') {
          this.extractHasMany(fixture, relationship, modelName, relationships);
        }
      }
    });
    return relationships;
  }

  /**
   Extract belongTo relationships
    @param fixture
   @param relationship
   @param relationships
   */
  extractBelongsTo(fixture, relationship, parentModelName, relationships) {
    let belongsToRecord = fixture[relationship.name],
      isEmbedded = this.isEmbeddedRelationship(parentModelName, relationship.name),
      relationshipKey = isEmbedded ? relationship.name : this.transformRelationshipKey(relationship, parentModelName);
    let data = this.extractSingleRecord(belongsToRecord, relationship, isEmbedded);
    relationships[relationshipKey] = this.assignRelationship(data);
  }

  // Borrowed from ember data
  // checks config for attrs option to embedded (always)
  isEmbeddedRelationship(modelName, attr) {
    let serializer = this.store.serializerFor(modelName),
      option = this.attrsOption(serializer, attr);
    return option && (option.embedded === 'always' || option.deserialize === 'records');
  }
  attrsOption(serializer, attr) {
    let attrs = serializer.get('attrs'),
      option = attrs && (attrs[camelize(attr)] || attrs[attr]);
    return option;
  }
  extractHasMany(fixture, relationship, parentModelName, relationships) {
    let hasManyRecords = fixture[relationship.name],
      relationshipKey = this.transformRelationshipKey(relationship, parentModelName),
      isEmbedded = this.isEmbeddedRelationship(parentModelName, relationship.name);
    if (hasManyRecords && hasManyRecords.isProxy) {
      return this.addListProxyData(hasManyRecords, relationship, relationships, isEmbedded, parentModelName);
    }
    if (typeOf(hasManyRecords) !== 'array') {
      return;
    }
    let records = hasManyRecords.map(hasManyRecord => {
      return this.extractSingleRecord(hasManyRecord, relationship, isEmbedded);
    });
    relationships[relationshipKey] = this.assignRelationship(records);
  }
  extractSingleRecord(record, relationship, isEmbedded) {
    let data;
    switch (typeOf(record)) {
      case 'object':
        if (record.isProxy) {
          data = this.addProxyData(record, relationship, isEmbedded);
        } else {
          data = this.addData(record, relationship, isEmbedded);
        }
        break;
      case 'instance':
        data = this.normalizeAssociation(record, relationship);
        break;
      case 'number':
      case 'string':
        assert(`Polymorphic relationships cannot be specified by id you
          need to supply an object with id and type`, !relationship.options.polymorphic);
        record = {
          id: record,
          type: relationship.type
        };
        data = this.normalizeAssociation(record, relationship);
    }
    return data;
  }
  assignRelationship(object) {
    return object;
  }

  /**
   * Technically don't have to verify the links because hey would not even be assigned,
   * but the user might want to know why
   *
   * @param modelName
   * @param links
   */
  verifyLinks(modelName, links = {}) {
    const modelClass = this.store.modelFor(modelName),
      relationships = modelClass.relationshipsByName;
    for (let [relationshipKey, link] of Object.entries(links)) {
      assert(`You defined a link url ${link} for the [${relationshipKey}] relationship
        on model [${modelName}] but that relationship does not exist`, relationships.get(relationshipKey));
    }
  }
  addData(embeddedFixture, relationship, isEmbedded) {
    let relationshipType = this.getRelationshipType(relationship, embeddedFixture),
      // find possibly more embedded fixtures
      data = this.convertSingle(relationshipType, embeddedFixture);
    if (isEmbedded) {
      return data;
    }
    this.addToIncluded(data, relationshipType);
    return this.normalizeAssociation(data, relationship);
  }

  // proxy data is data that was build with FactoryGuy.build method
  addProxyData(jsonProxy, relationship, isEmbedded) {
    let data = jsonProxy.getModelPayload(),
      relationshipType = this.getRelationshipType(relationship, data);
    if (isEmbedded) {
      this.addToIncludedFromProxy(jsonProxy);
      return data;
    }
    this.addToIncluded(data, relationshipType);
    this.addToIncludedFromProxy(jsonProxy);
    return this.normalizeAssociation(data, relationship);
  }

  // listProxy data is data that was build with FactoryGuy.buildList method
  addListProxyData(jsonProxy, relationship, relationships, isEmbedded, parentModelName) {
    let relationshipKey = this.transformRelationshipKey(relationship, parentModelName);
    let records = jsonProxy.getModelPayload().map(data => {
      if (isEmbedded) {
        return data;
      }
      let relationshipType = this.getRelationshipType(relationship, data);
      this.addToIncluded(data, relationshipType);
      return this.normalizeAssociation(data, relationship);
    });
    this.addToIncludedFromProxy(jsonProxy);
    relationships[relationshipKey] = this.assignRelationship(records);
  }
}

/**
 * Using `serializeMode` to create a payload the way ember-data would serialize types
 * when returning a payload to a server that accepts JSON-API wherin the types are
 * pluralized
 *
 */
class JSONAPIFixtureConverter extends FixtureConverter {
  constructor(store, {
    transformKeys = true,
    serializeMode = false
  } = {}) {
    super(store, {
      transformKeys,
      serializeMode
    });
    this.typeTransformFn = this.serializeMode ? this.typeTransformViaSerializer : dasherize;
    this.defaultKeyTransformFn = dasherize;
    this.polymorphicTypeTransformFn = dasherize;
    this.included = [];
  }
  typeTransformViaSerializer(modelName) {
    let serializer = this.store.serializerFor(modelName);
    return serializer.payloadKeyFromModelName(modelName);
  }
  emptyResponse(_, options = {}) {
    return {
      data: options.useValue || null
    };
  }

  /**
   * JSONAPISerializer does not use modelName for payload key,
   * and just has 'data' as the top level key.
   *
   * @param modelName
   * @param fixture
   * @returns {*}
   */
  createPayload(modelName, fixture) {
    return {
      data: fixture
    };
  }

  /**
   * Add the included data
   *
   * @param payload
   */
  addIncludedArray(payload) {
    if (!isEmpty(this.included)) {
      payload.included = this.included;
    }
  }

  /**
   In order to conform to the way ember data expects to handle relationships
   in a json api payload ( during deserialization ), convert a record ( model instance )
   into an object with type and id. Types are pluralized.
    @param {Object or DS.Model instance} record
   @param {Object} relationship
   */
  normalizeAssociation(record) {
    if (typeOf(record) === 'object') {
      return {
        type: this.typeTransformFn(record.type),
        id: record.id
      };
    } else {
      return {
        type: this.typeTransformFn(record.constructor.modelName),
        id: record.id
      };
    }
  }
  isEmbeddedRelationship(/*modelName, attr*/
  ) {
    return false;
  }

  /**
   Recursively descend into the fixture json, looking for relationships that are
   either record instances or other fixture objects that need to be normalized
   and/or included in the 'included' hash
    @param modelName
   @param fixture
   @param included
   @returns {{type: *, id: *, attributes}}
   */
  convertSingle(modelName, fixture) {
    let polymorphicType = fixture.type;
    if (polymorphicType && fixture._notPolymorphic) {
      polymorphicType = modelName;
      delete fixture._notPolymorphic;
    }
    let data = {
      type: this.typeTransformFn(polymorphicType || modelName),
      attributes: this.extractAttributes(modelName, fixture)
    };
    this.addPrimaryKey(modelName, data, fixture);
    let relationships = this.extractRelationships(modelName, fixture);
    this.verifyLinks(modelName, fixture.links);
    this.assignLinks(relationships, fixture.links);
    if (Object.getOwnPropertyNames(relationships).length > 0) {
      data.relationships = relationships;
    }
    return data;
  }

  /*
   Add the model to included array unless it's already there.
   */
  addToIncluded(data) {
    let found = A(this.included).find(model => {
      return model.id === data.id && model.type === data.type;
    });
    if (!found) {
      this.included.push(data);
    }
  }
  addToIncludedFromProxy(proxy) {
    proxy.includes().forEach(data => {
      this.addToIncluded(data);
    });
  }
  assignRelationship(object) {
    return {
      data: object
    };
  }

  /**
   * JSONAPI can have data and links in the same relationship definition
   * so do special handling to make it happen
   *
   *  json = build('user', {links: {company: '/user/1/company'}});
   *
   *  {
   *    data: {
   *      id: 1,
   *      type: 'user',
   *      attributes: {
   *        name: 'User1',
   *        style: "normal"
   *      },
   *      relationships: {
   *        company: {
   *          links: {related: '/user/1/company'}
   *        }
   *      }
   *    }
   *
   * @param relationshipData
   * @param links
   */
  assignLinks(relationshipData, links) {
    for (let [relationshipKey, link] of Object.entries(links || {})) {
      let data = relationshipData[relationshipKey];
      data = Object.assign({
        links: {
          related: link
        }
      }, data);
      relationshipData[relationshipKey] = data;
    }
  }
}

class FixtureBuilder {
  constructor(store, converterClass, payloadClass) {
    this.store = store;
    this.converterClass = converterClass;
    this.payloadClass = payloadClass;
  }
  getConverter(options) {
    return new this.converterClass(this.store, options);
  }
  wrapPayload(modelName, json, converter = this.getConverter()) {
    new this.payloadClass(modelName, json, converter);
  }

  /**
   * Transform an attribute key to what the serializer would expect.
   * Key should be attribute but relationships still work.
   *
   * @param modelName
   * @param key
   * @returns {*}
   */
  transformKey(modelName, key) {
    let converter = this.getConverter(),
      model = this.store.modelFor(modelName),
      relationshipsByName = model.relationshipsByName,
      relationship = relationshipsByName.get(key);
    if (relationship) {
      return converter.transformRelationshipKey(relationship, modelName);
    }
    let transformKeyFunction = converter.getTransformKeyFunction(modelName, 'Attribute');
    return transformKeyFunction(key);
  }

  /**
   Normalizes the serialized model to the expected API format
    @param modelName
   @param payload
   */
  normalize(modelName, payload) {
    return payload;
  }

  /**
   Convert fixture for FactoryGuy.build
    @param modelName
   @param fixture
   @param converterOptions
   */
  convertForBuild(modelName, fixture, converterOptions) {
    let converter = this.getConverter(converterOptions);
    if (!fixture) {
      return converter.emptyResponse(modelName, converterOptions);
    }
    let json = converter.convert(modelName, fixture);
    this.wrapPayload(modelName, json, converter);
    return json;
  }

  /**
   Convert to the ember-data JSONAPI adapter specification, since FactoryGuy#make
   pushes jsonapi data into the store. For make builds, don't transform attr keys,
   because the json is being pushed into the store directly
   ( not going through adapter/serializer layers )
    @param {String} modelName
   @param {String} fixture
   @returns {*} new converted fixture
   */
  convertForMake(modelName, fixture) {
    let converter = new JSONAPIFixtureConverter(this.store, {
      transformKeys: false
    });
    return converter.convert(modelName, fixture);
  }

  /**
   Convert simple ( older ember data format ) error hash:
    {errors: {description: ['bad']}}
    to:
    {errors: [{detail: 'bad', source: { pointer:  "data/attributes/description"}, title: 'invalid description'}] }
    @param errors simple error hash
   @returns {{}}  JSONAPI formatted errors
   */
  convertResponseErrors(object) {
    let jsonAPIErrors = [],
      {
        errors
      } = object;
    assert(`[ember-data-factory-guy] Your error response must have an errors key. 
      The errors hash format is: {errors: {name: ["name too short"]}}`, errors);
    for (let key in errors) {
      let description = typeOf(errors[key]) === 'array' ? errors[key][0] : errors[key],
        source = {
          pointer: 'data/attributes/' + key
        },
        newError = {
          detail: description,
          title: 'invalid ' + key,
          source: source
        };
      jsonAPIErrors.push(newError);
    }
    return {
      errors: jsonAPIErrors
    };
  }
}

class BasePayload {
  /**
   Proxy class for getting access to a json payload.
   Allows you to:
   - inspect a payload with friendly .get(attr)  syntax
   - add to json payload with more json built from build and buildList methods.
    @param {String} modelName name of model for payload
   @param {Object} json json payload being proxied
   @param {Boolean} converter the converter that built this json
   */
  constructor(modelName, json, converter) {
    this.modelName = modelName;
    this.json = json;
    this.converter = converter;
    this.listType = converter.listType || false;
    this.proxyMethods = w('getModelPayload isProxy get add unwrap');
    this.wrap(this.proxyMethods);
  }

  /**
   Add another json payload or meta data to this payload
    Typically you would build a payload and add that to another one
    Usage:
   ```
   let batMen = buildList('bat_man', 2);
   let user = build('user').add(batMen);
   ```
    but you can also add meta data:
   ```
   let user = buildList('user', 2).add({meta: { next: '/url?page=3', previous: '/url?page=1'}});
   ```
    @param {Object} optional json built from FactoryGuy build or buildList or
   meta data to add to payload
   @returns {Object} the current json payload
   */
  add(more) {
    this.converter.included = this.json;
    A(Object.getOwnPropertyNames(more)).reject(key => A(this.proxyMethods).includes(key)).forEach(key => {
      if (typeOf(more[key]) === 'array') {
        more[key].forEach(data => this.converter.addToIncluded(data, key));
      } else {
        if (key === 'meta') {
          this.addMeta(more[key]);
        } else {
          this.converter.addToIncluded(more[key], key);
        }
      }
    });
    return this.json;
  }

  /**
   Add new meta data to the json payload, which will
   overwrite any existing meta data with same keys
    @param {Object} data meta data to add
   */
  addMeta(data) {
    this.json.meta = this.json.meta || {};
    Object.assign(this.json.meta, data);
  }

  // marker function for saying "I am a proxy"
  isProxy() {}

  // get the top level model's payload ( without the includes or meta data )
  getModelPayload() {
    return this.get();
  }

  // each subclass has unique proxy methods to add to the basic
  addProxyMethods(methods) {
    this.proxyMethods = this.proxyMethods.concat(methods);
    this.wrap(methods);
  }

  // add proxy methods to json object
  wrap(methods) {
    methods.forEach(method => this.json[method] = this[method].bind(this));
  }

  // remove proxy methods from json object
  unwrap() {
    this.proxyMethods.forEach(method => delete this.json[method]);
  }

  /**
   Main access point for most users to get data from the
   json payload
    Could be asking for attribute like 'id' or 'name',
   or index into list for list type like 0 or 1
    @param key
   @returns {*}
   */
  get(key) {
    if (this.listType) {
      return this.getListKeys(key);
    }
    return this.getObjectKeys(key);
  }
}

class JSONAPIPayload extends BasePayload {
  constructor(modelName, json, converter) {
    super(modelName, json, converter);
    this.data = json.data;
    this.addProxyMethods(['includes']);
  }
  getModelPayload() {
    return this.data;
  }

  /**
   * Override base add method for special json-api handling to
   * add more things to payload like meta or more json to sideload
   *
   * @param more
   */
  add(more) {
    if (more.meta) {
      this.addMeta(more.meta);
    } else {
      if (!this.json.included) {
        this.json.included = [];
      }
      this.converter.included = this.json.included;
      // add the main moreJson model payload
      let data = more.getModelPayload();
      if (typeOf(data) === 'array') {
        data.forEach(dati => this.converter.addToIncluded(dati));
      } else {
        this.converter.addToIncluded(data);
      }
      // add all of the moreJson's includes
      this.converter.addToIncludedFromProxy(more);
    }
    return this.json;
  }
  createAttrs(data) {
    let relationships = {};
    Object.keys(data.relationships || []).forEach(key => {
      relationships[key] = data.relationships[key].data;
    });
    let attrs = Object.assign({}, data.attributes, relationships);
    attrs.id = data.id;
    return attrs;
  }
  includes() {
    return this.json.included || [];
  }
  getObjectKeys(key) {
    let attrs = this.createAttrs(this.data);
    if (!key) {
      return attrs;
    }
    if (attrs[key]) {
      return attrs[key];
    }
  }
  getListKeys(key) {
    let attrs = this.data.map(data => this.createAttrs(data));
    if (isEmpty(key)) {
      return attrs;
    }
    if (typeof key === 'number') {
      return attrs[key];
    }
    if (key === 'firstObject') {
      return attrs[0];
    }
    if (key === 'lastObject') {
      return attrs[attrs.length - 1];
    }
  }
}

/**
 Fixture Builder for JSONAPISerializer
 */
class JSONAPIFixtureBuilder extends FixtureBuilder {
  constructor(store) {
    super(store, JSONAPIFixtureConverter, JSONAPIPayload);
    this.updateHTTPMethod = 'PATCH';
  }
}

function param(obj, prefix) {
  let str = [],
    p;
  for (p in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, p)) {
      var k = prefix ? prefix + '[' + (isNaN(p) ? p : '') + ']' : p,
        v = obj[p];
      str.push(v !== null && typeof v === 'object' ? param(v, k) : encodeURIComponent(k) + '=' + encodeURIComponent(v));
    }
  }
  return str.join('&');
}
function isEmptyObject(obj) {
  return !isObject(obj) || Object.keys(obj).length === 0;
}

/**
 * Simple object check.
 * @param item
 * @returns {boolean}
 */
function isObject(item) {
  return item && typeof item === 'object' && !Array.isArray(item);
}

/**
 * Deep merge two objects.
 * @param target
 * @param ...sources
 */
function mergeDeep(target, ...sources) {
  if (!sources.length) return target;
  const source = sources.shift();
  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        if (isObject(source[key])) {
          if (!target[key]) {
            Object.assign(target, {
              [key]: {}
            });
          }
          mergeDeep(target[key], source[key]);
        } else {
          Object.assign(target, {
            [key]: source[key]
          });
        }
      }
    }
  }
  return mergeDeep(target, ...sources);
}
function isEquivalent(a, b) {
  var type = typeOf(a);
  if (type !== typeOf(b)) {
    return false;
  }
  switch (type) {
    case 'object':
      return objectIsEquivalent(a, b);
    case 'array':
      return arrayIsEquivalent(a, b);
    default:
      return a === b;
  }
}
function arrayIsEquivalent(arrayA, arrayB) {
  if (arrayA.length !== arrayB.length) {
    return false;
  }
  return arrayA.every(function (item, index) {
    return isEquivalent(item, arrayB[index]);
  });
}
function objectIsEquivalent(objectA, objectB) {
  let aProps = Object.keys(objectA),
    bProps = Object.keys(objectB);
  if (aProps.length !== bProps.length) {
    return false;
  }
  for (let i = 0; i < aProps.length; i++) {
    let propName = aProps[i],
      aEntry = objectA[propName],
      bEntry = objectB[propName];
    if (!isEquivalent(aEntry, bEntry)) {
      return false;
    }
  }
  return true;
}

/**
 * Used to split a url with query parms into url and queryParams
 * MockLinks and Mock both use this
 *
 * @param url
 * @returns {*[]}
 */
function parseUrl(url) {
  const [urlPart, query] = (url || '').split('?');
  const params = query && query.split('&').reduce((params, param) => {
    let [key, value] = param.split('=');
    params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : '';
    return params;
  }, {}) || {};
  return [urlPart, params];
}

/**
 Convert base fixture to a JSON format payload.

 @param store
 @constructor
 */
class JSONFixtureConverter extends FixtureConverter {
  constructor(store, options) {
    super(store, options);
    this.defaultKeyTransformFn = underscore;
    this.polymorphicTypeTransformFn = underscore;
  }

  /**
   * Can't add to payload since sideloading not supported
   *
   * @param moreJson
   */
  add(/*moreJson*/) {}

  /**
   * There is no payload key for JSON Serializer
   *
   * @param modelName
   * @param fixture
   * @returns {*}
   */
  createPayload(_, fixture) {
    return fixture;
  }

  /**
   * There is no sideloading for JSON Serializer
   *
   * @param payload
   */
  addIncludedArray(/*payload*/) {}

  /**
   Convert single record
    @param {String} modelName
   @param {Object} fixture
   */
  convertSingle(modelName, fixture) {
    let data = {},
      attributes = this.extractAttributes(modelName, fixture),
      relationships = this.extractRelationships(modelName, fixture);
    Object.keys(attributes).forEach(key => {
      data[key] = attributes[key];
    });
    Object.keys(relationships).forEach(key => {
      data[key] = relationships[key];
    });
    this.addPrimaryKey(modelName, data, fixture);
    this.verifyLinks(modelName, fixture.links);
    this.assignLinks(data, fixture.links);
    return data;
  }
  transformRelationshipKey(relationship, parentModelName) {
    let transformedKey = super.transformRelationshipKey(relationship, parentModelName);
    if (relationship.options.polymorphic) {
      transformedKey = transformedKey.replace('_id', '');
    }
    return transformedKey;
  }

  /**
    @param {Object} record
   @param {Object} relationship
   */
  normalizeAssociation(record, relationship) {
    if (this.serializeMode) {
      return record.id;
    }
    if (typeOf(record) === 'object') {
      if (relationship.options.polymorphic) {
        return {
          type: dasherize(record.type),
          id: record.id
        };
      } else {
        return record.id;
      }
    }
    // it's a model instance
    if (relationship.options.polymorphic) {
      return {
        type: dasherize(record.constructor.modelName),
        id: record.id
      };
    }
    return record.id;
  }

  /**
   * JSON/REST links can be placed in the data exactly as they appear
   * on the fixture definition
   *
   *   json = build('user', {links: {properties: '/user/1/properties'}});
   *
   *    {
   *      user: {
   *        id: 1,
   *        name: 'User1',
   *        style: "normal",
   *        links: { properties: '/user/1/properties' }
   *      }
   *    }
   *
   * @param data
   * @param links
   */
  assignLinks(data, links) {
    if (!isEmptyObject(links)) {
      data.links = links;
    }
  }

  /**
   The JSONSerializer does not support sideloading records
    @param {String} modelKey
   @param {Object} data
   @param {Object} includeObject
   */
  addToIncluded(/*data, modelKey*/) {}

  /**
   The JSONSerializer does not support sideloading records
    @param proxy json payload proxy
   */
  addToIncludedFromProxy(/*proxy*/) {}
}

/**
 Convert base fixture to a REST Serializer formatted payload.

 @param store
 @constructor
 */
class RESTFixtureConverter extends JSONFixtureConverter {
  constructor(store, options) {
    super(store, options);
    this.included = {};
  }
  emptyResponse(modelName, options = {}) {
    return {
      [modelName]: options.useValue || null
    };
  }

  /**
   * RESTSerializer has a payload key
   *
   * @param modelName
   * @param fixture
   * @returns {*}
   */
  createPayload(modelName, fixture) {
    return {
      [this.getPayloadKey(modelName)]: fixture
    };
  }

  /**
   * Get the payload key for this model from the serializer
   *
   * @param modelName
   * @returns {*}
   */
  getPayloadKey(modelName) {
    let serializer = this.store.serializerFor(modelName),
      payloadKey = modelName;
    // model fragment serializer does not have payloadKeyFromModelName method
    if (serializer.payloadKeyFromModelName) {
      payloadKey = serializer.payloadKeyFromModelName(modelName);
    }
    return this.listType ? pluralize(payloadKey) : payloadKey;
  }

  /**
   * Add the included data to the final payload
   *
   * @param payload
   */
  addIncludedArray(payload) {
    Object.keys(this.included).forEach(key => {
      if (!payload[key]) {
        payload[key] = this.included[key];
      } else {
        Array.prototype.push.apply(payload[key], this.included[key]);
      }
    });
  }

  /**
   Add the model to included array unless it's already there.
    @param {String} modelKey
   @param {Object} data
   @param {Object} includeObject
   */
  addToIncluded(data, modelKey) {
    let relationshipKey = pluralize(dasherize(modelKey));
    if (!this.included[relationshipKey]) {
      this.included[relationshipKey] = [];
    }
    let modelRelationships = this.included[relationshipKey],
      found = A(modelRelationships).find(existing => existing.id === data.id);
    if (!found) {
      modelRelationships.push(data);
    }
  }

  /**
   Add proxied json to this payload, by taking all included models and
   adding them to this payloads includes
    @param proxy json payload proxy
   */
  addToIncludedFromProxy(proxy) {
    proxy.includeKeys().forEach(modelKey => {
      let includedModels = proxy.getInclude(modelKey);
      includedModels.forEach(data => {
        this.addToIncluded(data, modelKey);
      });
    });
  }
}

class RESTPayload extends BasePayload {
  constructor(modelName, json, converter) {
    super(modelName, json, converter);
    this.payloadKey = converter.getPayloadKey(modelName);
    this.addProxyMethods(['includeKeys', 'getInclude']);
  }
  includeKeys() {
    let keys = A(Object.keys(this.json)).reject(key => this.payloadKey === key);
    return A(keys).reject(key => A(this.proxyMethods).includes(key)) || [];
  }
  getInclude(modelType) {
    return this.json[modelType];
  }
  getObjectKeys(key) {
    let attrs = this.json[this.payloadKey];
    if (isEmpty(key)) {
      return attrs;
    }
    return attrs[key];
  }
  getListKeys(key) {
    let attrs = this.json[this.payloadKey];
    if (isEmpty(key)) {
      return attrs;
    }
    if (typeof key === 'number') {
      return attrs[key];
    }
    if (key === 'firstObject') {
      return attrs[0];
    }
    if (key === 'lastObject') {
      return attrs[attrs.length - 1];
    }
  }
}

/**
 Fixture Builder for REST based Serializer, like ActiveModelSerializer or
 RESTSerializer

 */
class RESTFixtureBuilder extends FixtureBuilder {
  constructor(store) {
    super(store, RESTFixtureConverter, RESTPayload);
  }
  /**
   Map single object to response json.
    Allows custom serializing mappings and meta data to be added to requests.
    @param {String} modelName model name
   @param {Object} json Json object from record.toJSON
   @return {Object} responseJson
   */
  normalize(modelName, payload) {
    return {
      [modelName]: payload
    };
  }
}

class JSONPayload extends BasePayload {
  /** 
   Can't add to included array for JSON payloads since they have
   no includes or sideloaded relationships
    Meta not working at the moment for this serializer even though
   it is being included here in the payload
   */
  add(more) {
    if (more.meta) {
      this.addMeta(more.meta);
    }
    return this.json;
  }
  getObjectKeys(key) {
    let attrs = this.json;
    if (isEmpty(key)) {
      return JSON.parse(JSON.stringify(attrs));
    }
    return attrs[key];
  }
  getListKeys(key) {
    let attrs = this.json;
    if (isEmpty(key)) {
      return JSON.parse(JSON.stringify(attrs));
    }
    if (typeof key === 'number') {
      return attrs[key];
    }
    if (key === 'firstObject') {
      return attrs[0];
    }
    if (key === 'lastObject') {
      return attrs[attrs.length - 1];
    }
  }
}

/**
 Fixture Builder for JSONSerializer

 */
class JSONFixtureBuilder extends FixtureBuilder {
  constructor(store) {
    super(store, JSONFixtureConverter, JSONPayload);
  }
  /**
   Map single object to response json.
    Allows custom serializing mappings and meta data to be added to requests.
    @param {String} modelName model name
   @param {Object} json Json object from record.toJSON
   @return {Object} responseJson
   */
  normalize(_, payload) {
    return payload;
  }
}

/**
 Convert base fixture to the ActiveModel Serializer expected payload.
 */
class AMSFixtureConverter extends RESTFixtureConverter {
  /**
   * In `serializeMode` use convert a relationship from "company" to "company_id"
   * which REST / JSON converters override to strip that _id
   *
   * @param relationship
   * @returns {*}
   */
  transformRelationshipKey(relationship, parentModelName) {
    if (this.serializeMode) {
      let transformFn = this.getTransformKeyFunction(relationship.type, 'Relationship');
      return transformFn(relationship.name, relationship.kind);
    } else {
      return super.transformRelationshipKey(relationship, parentModelName);
    }
  }
}

/**
 Fixture Builder for ActiveModelSerializer
 */
class ActiveModelFixtureBuilder extends FixtureBuilder {
  constructor(store) {
    super(store, AMSFixtureConverter, RESTPayload);
  }

  /**
   ActiveModelAdapter converts them automatically for status 422
    @param errors
   @returns {*}
   */
  convertResponseErrors(errors, status) {
    if (status === 422) {
      return errors;
    } else {
      return super.convertResponseErrors(errors, status);
    }
  }

  /**
   Map single object to response json.
    Allows custom serializing mappings and meta data to be added to requests.
    @param {String} modelName model name
   @param {Object} json Json object from record.toJSON
   @return {Object} responseJson
   */
  normalize(modelName, payload) {
    return {
      [modelName]: payload
    };
  }
}

let ActiveModelSerializer;
if (macroCondition(dependencySatisfies('active-model-adapter', '*'))) {
  ActiveModelSerializer = importSync('active-model-adapter').ActiveModelSerializer;
}
class FixtureBuilderFactory {
  constructor(store) {
    this.store = store;
  }

  /**
   Return appropriate FixtureBuilder for the model's serializer type
   */
  fixtureBuilder(modelName) {
    let serializer = this.store.serializerFor(modelName);
    if (!serializer) {
      return new JSONAPIFixtureBuilder(this.store);
    }
    if (this.usingJSONAPISerializer(serializer)) {
      return new JSONAPIFixtureBuilder(this.store);
    }
    if (this.usingActiveModelSerializer(serializer)) {
      return new ActiveModelFixtureBuilder(this.store);
    }
    if (this.usingRESTSerializer(serializer)) {
      return new RESTFixtureBuilder(this.store);
    }
    if (this.usingJSONSerializer(serializer)) {
      return new JSONFixtureBuilder(this.store);
    }
    return new JSONAPIFixtureBuilder(this.store);
  }
  usingJSONAPISerializer(serializer) {
    return serializer instanceof JSONAPISerializer;
  }
  usingActiveModelSerializer(serializer) {
    return ActiveModelSerializer && serializer instanceof ActiveModelSerializer;
  }
  usingRESTSerializer(serializer) {
    return serializer instanceof RESTSerializer;
  }
  usingJSONSerializer(serializer) {
    return serializer instanceof JSONSerializer;
  }
}

function _defineProperty(e, r, t) {
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
    value: t,
    enumerable: true,
    configurable: true,
    writable: true
  }) : e[r] = t, e;
}
function _toPrimitive(t, r) {
  if ("object" != typeof t || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (void 0 !== e) {
    var i = e.call(t, r);
    if ("object" != typeof i) return i;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
  var i = _toPrimitive(t, "string");
  return "symbol" == typeof i ? i : i + "";
}

function Sequence (fn) {
  let index = 1;
  this.next = function () {
    return fn.call(this, index++);
  };
  this.reset = function () {
    index = 1;
  };
}

function MissingSequenceError (message) {
  this.toString = function () {
    return message;
  };
}

let _stringsOnly = false;
let _interceptor = 'pretender';
if (macroCondition(getOwnConfig()?.useStringIdsOnly)) {
  _stringsOnly = true;
} else {
  _stringsOnly = false;
}
if (macroCondition(getOwnConfig()?.interceptor === 'msw')) {
  _interceptor = 'msw';
} else if (macroCondition(getOwnConfig()?.interceptor === 'pretender')) {
  _interceptor = 'pretender';
}

/**
 * A function to help check if an id conforms to the configuration of useStringIdsOnly
 *
 * Ensures either the id is string, or they've disabled useStringIdsOnly
 */
function verifyId(id) {
  assert(`[ember-data-factory-guy]: non-string id given, but useStringIdsOnly config is set`, typeof id === 'string' || !useStringIdsOnly);
}
const useStringIdsOnly = _stringsOnly;
const interceptor = _interceptor;

/**
 * A wrapper around generated ids to help with handling them, regardless of string vs int id type.
 * Keeps track of next id to use for a model definition when creating a new record.
 */
class IdGenerator {
  constructor() {
    _defineProperty(this, "newId", useStringIdsOnly ? '1' : 1);
  }
  // should always be an unused id ready for use

  /**
   * An unused id is requested. Return the unused id, and cycle it to make a new one.
   */
  nextId() {
    const {
      newId
    } = this;
    this.newId = typeof newId === 'string' ? (parseInt(newId, 10) + 1).toString() : newId + 1;
    return newId;
  }
}

/**
 A ModelDefinition encapsulates a model's definition

 @param model
 @param config
 @constructor
 */
class ModelDefinition {
  constructor(model, config) {
    this.modelName = model;
    this.idGenerator = new IdGenerator();
    this.originalConfig = mergeDeep({}, config);
    this.parseConfig(Object.assign({}, config));
  }

  /**
   Returns a model's full relationship if the field is a relationship.
    @param {String} field  field you want to relationship info for
   @returns {DS.Relationship} relationship object if the field is a relationship, null if not
   */
  getRelationship(field) {
    let modelClass = factoryGuy.store.modelFor(this.modelName);
    let relationship = modelClass.relationshipsByName.get(field);
    return relationship || null;
  }

  /**
   @param {String} name model name like 'user' or named type like 'admin'
   @returns {Boolean} true if name is this definitions model or this definition
   contains a named model with that name
   */
  matchesName(name) {
    return this.modelName === name || this.namedModels[name];
  }

  /**
   Call the next method on the named sequence function. If the name
   is a function, create the sequence with that function
    @param   {String} name previously declared sequence name or
   an the random name generate for inline functions
   @param   {Function} sequenceFn optional function to use as sequence
   @returns {String} output of sequence function
   */
  generate(name, sequenceFn) {
    if (sequenceFn) {
      if (!this.sequences[name]) {
        // create and add that sequence function on the fly
        this.sequences[name] = new Sequence(sequenceFn);
      }
    }
    let sequence = this.sequences[name];
    if (!sequence) {
      throw new MissingSequenceError(`[ember-data-factory-guy] Can't find that sequence named [${name}] in '${this.modelName}' definition`);
    }
    return sequence.next();
  }

  /**
   Build a fixture by name
    @param {String} name fixture name
   @param {Object} opts attributes to override
   @param {String} traitArgs array of traits
   @returns {Object} json
   */
  build(name, opts = {}, traitNames = [], buildType = 'build') {
    let modelAttributes = this.namedModels[name] || {};

    // merge default, modelAttributes, traits and opts to get the rough fixture
    let fixture = Object.assign({}, this.default, modelAttributes);

    // set the id, unless it was already set in opts
    if (!fixture.id && !opts.id) {
      fixture.id = this.idGenerator.nextId();
    }
    if (this.notPolymorphic !== undefined) {
      fixture._notPolymorphic = true;
    }
    traitNames.forEach(traitName => {
      let trait = this.traits[traitName];
      assert(`[ember-data-factory-guy] You're trying to use a trait [${traitName}] 
        for model ${this.modelName} but that trait can't be found.`, trait);
      if (typeOf(trait) === 'function') {
        trait(fixture);
      }
      Object.assign(fixture, trait);
    });
    Object.assign(fixture, opts);
    for (let attribute in fixture) {
      let attributeType = typeOf(fixture[attribute]);
      if (attributeType === 'function') {
        this.addFunctionAttribute(fixture, attribute, buildType);
      } else if (attributeType === 'object') {
        this.addObjectAttribute(fixture, attribute, buildType);
      }
    }
    verifyId(fixture.id);
    return fixture;
  }

  // function might be a sequence, an inline attribute function or an association
  addFunctionAttribute(fixture, attribute, buildType) {
    fixture[attribute] = fixture[attribute].call(this, fixture, buildType);
  }
  addObjectAttribute(fixture, attribute, buildType) {
    // If it's an object and it's a model association attribute, build the json
    // for the association and replace the attribute with that json
    let relationship = this.getRelationship(attribute);
    if (relationship) {
      let payload = fixture[attribute];
      if (!payload.isProxy && !payload.links) {
        fixture[attribute] = factoryGuy.buildRaw({
          name: relationship.type,
          opts: payload,
          buildType
        });
      }
    }
  }

  /**
   Build a list of fixtures
    @param {String} name model name or named model type
   @param {Integer} number of fixtures to build
   @param {Array} array of traits to build with
   @param {Object} opts attribute options
   @returns array of fixtures
   */
  buildList(name, number, traits, opts, buildType) {
    return Array(number).fill().map(() => this.build(name, opts, traits, buildType));
  }

  // Reset the id sequence, and reset the sequences
  reset() {
    this.idGenerator = new IdGenerator();
    for (let name in this.sequences) {
      this.sequences[name].reset();
    }
  }
  hasAfterMake() {
    return !!this.afterMake;
  }
  applyAfterMake(model, opts) {
    if (this.afterMake) {
      // passed in options override transient setting
      let options = Object.assign({}, this.transient, opts);
      this.afterMake(model, options);
    }
  }

  /*
   Need special 'merge' function to be able to merge objects with functions
    @param newConfig
   @param config
   @param otherConfig
   @param section
   */
  mergeSection(config, otherConfig, section) {
    let attr;
    if (otherConfig[section]) {
      if (!config[section]) {
        config[section] = {};
      }
      for (attr in otherConfig[section]) {
        if (!config[section][attr]) {
          config[section][attr] = otherConfig[section][attr];
        }
      }
    }
  }

  /**
   When extending another definition, merge it with this one by:
   merging only sequences, default section and traits
    @param {Object} config
   @param {ModelDefinition} otherDefinition
   */
  merge(config, otherDefinition) {
    let otherConfig = mergeDeep({}, otherDefinition.originalConfig);
    delete otherConfig.extends;
    this.mergeSection(config, otherConfig, 'sequences');
    // not sure why I have to use main definition for default,
    // but it works, so umm .. errr .. yeah
    this.mergeSection(config, otherDefinition, 'default');
    this.mergeSection(config, otherConfig, 'traits');
  }
  mergeConfig(config) {
    let extending = config.extends;
    let definition = factoryGuy.findModelDefinition(extending);
    assert(`[ember-data-factory-guy] You are trying to extend [${this.modelName}] with [ ${extending} ].
      But FactoryGuy can't find that definition [ ${extending} ]
      you are trying to extend. Make sure it was created/imported before
      you define [ ${this.modelName} ]`, definition);
    this.merge(config, definition);
  }
  parseDefault(config) {
    this.default = config.default || {};
    delete config.default;
  }
  parseTraits(config) {
    this.traits = config.traits || {};
    delete config.traits;
  }
  parseTransient(config) {
    this.transient = config.transient || {};
    delete config.transient;
  }
  parseCallBacks(config) {
    this.afterMake = config.afterMake;
    delete config.afterMake;
  }
  parsePolymorphicSetting(config) {
    if (config.polymorphic !== undefined && config.polymorphic === false) {
      this.notPolymorphic = true;
      delete config.polymorphic;
    }
  }
  parseSequences(config) {
    this.sequences = config.sequences || {};
    delete config.sequences;
    for (let sequenceName in this.sequences) {
      let sequenceFn = this.sequences[sequenceName];
      if (typeOf(sequenceFn) !== 'function') {
        throw new Error(`Problem with [${sequenceName}] sequence definition.
          Sequences must be functions`);
      }
      this.sequences[sequenceName] = new Sequence(sequenceFn);
    }
  }
  parseConfig(config) {
    if (config.extends) {
      this.mergeConfig(config);
    }
    this.parsePolymorphicSetting(config);
    this.parseSequences(config);
    this.parseTraits(config);
    this.parseDefault(config);
    this.parseTransient(config);
    this.parseCallBacks(config);
    this.namedModels = config;
  }
}

/**
 * RequestManager controls setting up a request mocking system to handle the mocks that are
 * created.
 *
 * For each request type / url like [GET /users] or [POST /user/1]
 * the request manager will assign a RequestWrapper class to handle it's response.
 *
 * This class will take the mock handler classes and assign them to a wrapper,
 * and also allow you to remove the handler or replace it from it's current
 * wrapper to new one.
 */
class RequestManager {
  constructor() {
    _defineProperty(this, "_settings", {});
    _defineProperty(this, "wrappers", {});
  }
  settings(settings) {
    this._settings = settings;
  }

  // eslint-disable-next-line no-unused-vars
  addHandler(_handler) {}

  /**
   * Remove a handler from the wrapper it was in
   *
   * @param handler
   */
  removeHandler(handler) {
    // get the old type, url info from last mockId
    // in order to find the wrapper it was in
    let {
        type,
        url
      } = handler.mockId,
      key = getKey(type, url),
      wrapper = this.wrappers[key];
    if (wrapper) {
      wrapper.removeHandler(handler);
    }
  }

  /**
   * Replace a handler from old wrapper to new one
   *
   * @param handler
   */
  replaceHandler(handler) {
    this.removeHandler(handler);
    this.addHandler(handler);
  }

  // used for testing
  findWrapper({
    handler,
    type,
    url
  }) {
    if (handler) {
      type = handler.getType();
      url = handler.getUrl();
    }
    let key = getKey(type, url);
    return this.wrappers[key];
  }
  async start() {}
  stop() {}
}
function getKey(type, url) {
  return [type, url].join(' ');
}
function getTypeUrl(handler) {
  return {
    type: handler.getType(),
    url: handler.getUrl()
  };
}

/**
 * Give each handler a mockId that is an object that holds information
 * about what it is mocking { type, url, num }
 *
 * @param {String} type like GET or POST
 * @param {String} url like '/users'
 * @param {Number} num a sequential number for each handler
 * @param handler
 */
function assignMockId(type, url, num, handler) {
  handler.mockId = {
    type,
    url,
    num
  };
}

let modelDefinitions = {};
class FactoryGuy {
  /**
   * Setting for FactoryGuy.
   *
   * logLevel: 0 is off, 1 is on
   *
   * @param logLevel [0/1]
   */
  settings({
    logLevel = 0
  } = {}) {
    this.logLevel = logLevel;
  }
  setRequestManager(aRequestManager) {
    assert(`[ember-data-factory-guy] FactoryGuy#setRequestManager needs a valid request manager instance. You passed in [${aRequestManager}]`, aRequestManager instanceof RequestManager);
    this.requestManager = aRequestManager;
  }
  setStore(aStore) {
    assert(`[ember-data-factory-guy] FactoryGuy#setStore needs a valid store instance. You passed in [${aStore}]`, aStore instanceof Store);
    this.store = aStore;
    this.fixtureBuilderFactory = new FixtureBuilderFactory(this.store);
  }
  fixtureBuilder(modelName) {
    return this.fixtureBuilderFactory.fixtureBuilder(modelName);
  }
  updateHTTPMethod(modelName) {
    return this.fixtureBuilder(modelName).updateHTTPMethod || 'PUT';
  }

  /**
   ```javascript
    class Person extends Model {
     @attr('string') type
     @attr('string') name
   }
    FactoryGuy.define('person', {
     sequences: {
       personName: function(num) {
         return 'person #' + num;
       },
       personType: function(num) {
         return 'person type #' + num;
       }
     },
     default: {
       type: 'normal',
       name: FactoryGuy.generate('personName')
     },
     dude: {
       type: FactoryGuy.generate('personType')
     },
   });
    ```
    For the Person model, you can define named fixtures like 'dude' or
   just use 'person' and get default values.
    And to get those fixtures you would call them this way:
    FactoryGuy.build('dude') or FactoryGuy.build('person')
    @param {String} model the model to define
   @param {Object} config your model definition
   */
  define(model, config) {
    modelDefinitions[model] = new ModelDefinition(model, config);
  }

  /*
   @param model name of named fixture type like: 'admin' or model name like 'user'
   @returns {ModelDefinition} if there is one matching that name
   */
  findModelDefinition(model) {
    return modelDefinitions[model];
  }
  getModelDefinitions() {
    return modelDefinitions;
  }

  /**
   Used in model definitions to declare use of a sequence. For example:
    ```
    FactoryGuy.define('person', {
     sequences: {
       personName: function(num) {
         return 'person #' + num;
       }
     },
     default: {
       name: FactoryGuy.generate('personName')
     }
   });
    ```
    @param {String|Function} nameOrFunction value previously declared sequence name or
   an inline function to use as the sequence
   @returns {Function} wrapper function that is called by the model
   definition containing the sequence
   */
  generate(nameOrFunction) {
    let sortaRandomName = Math.floor((1 + Math.random()) * 65536).toString(16) + Date.now();
    return function () {
      // this function will be called by ModelDefinition, which has it's own generate method
      if (typeOf(nameOrFunction) === 'function') {
        return this.generate(sortaRandomName, nameOrFunction);
      } else {
        return this.generate(nameOrFunction);
      }
    };
  }

  /**
   Used in model definitions to define a belongsTo association attribute.
   For example:
    ```
   FactoryGuy.define('project', {
       default: {
         title: 'Project'
       },
        // setup named project with built in associated user
       project_with_admin: {
         user: FactoryGuy.belongsTo('admin')
       }
        // or use as a trait
       traits: {
         with_admin: {
           user: FactoryGuy.belongsTo('admin')
         }
       }
     })
   ```
    @param {String} name  fixture name
   @param {String} trait  optional trait names ( one or more )
   @param {Object} opts  optional fixture options that will override default fixture values
   @returns {Function} wrapper function that will build the association json
   */
  belongsTo(...originalArgs) {
    let args = FactoryGuy.extractArguments(...originalArgs);
    return (_fixture, buildType) => this.buildRaw(Object.assign(args, {
      buildType
    }));
  }

  /**
   Used in model definitions to define a hasMany association attribute.
   For example:
    ```
   FactoryGuy.define('user', {
     default: {
       name: 'Bob'
     },
      // define the named user type that will have projects
     user_with_projects: { FactoryGuy.hasMany('project', 2) }
      // or use as a trait
     traits: {
       with_projects: {
         projects: FactoryGuy.hasMany('project', 2)
       }
     }
   })
    ```
    @param {String} fixtureName fixture name
   @param {Number} number optional number of hasMany association items to build
   @param {String} trait optional trait names ( one or more )
   @param {Object} opts options
   @returns {Function} wrapper function that will build the association json
   */
  hasMany(...originalArgs) {
    let args = FactoryGuy.extractListArguments(...originalArgs);
    return (_fixture, buildType) => this.buildRawList(Object.assign(args, {
      buildType
    }));
  }

  /**
   Build fixtures for model or specific fixture name.
    For example:
    ```
    FactoryGuy.build('user') for User model
   FactoryGuy.build('bob') for a 'bob' User
   FactoryGuy.build('bob', 'dude') for a 'bob' User with dude traits
   FactoryGuy.build('bob', 'dude', 'funny') for a 'bob' User with dude and funny traits
   FactoryGuy.build('bob', 'dude', {name: 'wombat'}) for a 'bob' User with dude trait and custom attribute name of 'wombat'
    ```
    @param {String} name  fixture name
   @param {String} trait  optional trait names ( one or more )
   @param {Object} opts  optional fixture options that will override default fixture values
   @returns {Object} json fixture
   */
  build(...originalArgs) {
    let args = FactoryGuy.extractArguments(...originalArgs),
      modelName = FactoryGuy.lookupModelForFixtureName(args.name, true),
      fixture = this.buildRaw(Object.assign(args, {
        buildType: 'build'
      }));
    return this.fixtureBuilder(modelName).convertForBuild(modelName, fixture);
  }

  /**
   Find the factory definition and use that to build the fixture
    @param name fixture name
   @param {Array} traits trait names
   @param {Object} opts  fixture options that will override default fixture values
   @param buildType 'build' or 'make'
   @returns {Object}
   */
  buildRaw({
    name,
    opts,
    traits,
    buildType = 'build'
  } = {}) {
    let definition = FactoryGuy.lookupDefinitionForFixtureName(name, true);
    return definition.build(name, opts, traits, buildType);
  }

  /**
   Build list of fixtures for model or specific fixture name. For example:
    ```
    FactoryGuy.buildList('user') // for 0 User models
   FactoryGuy.buildList('user', 2) // for 2 User models
   FactoryGuy.build('bob', 2) // for 2 User model with bob attributes
   FactoryGuy.build('bob', 'with_car', ['with_car',{name: "Dude"}])
   // 2 User model with bob attributes, where the first also has 'with_car' trait
   // the last has 'with_car' trait and name of "Dude"
    ```
    @param {String} name  fixture name
   @param {Number} number optional number of fixtures to build
   @param {String} trait  optional traits (one or more)
   @param {Object} opts  optional fixture options that will override default fixture values
   @returns {Array} list of fixtures
   */
  buildList(...args) {
    this.ensureNameInArguments('buildList', args);
    args = FactoryGuy.extractListArguments(...args);
    let list = this.buildRawList(Object.assign(args, {
        buildType: 'build'
      })),
      modelName = FactoryGuy.lookupModelForFixtureName(args.name);
    return this.fixtureBuilder(modelName).convertForBuild(modelName, list);
  }

  /**
   Find the factory definition and use that to build the fixture.
    @param name fixture name
   @param {Array} traits trait names
   @param {Object} opts  fixture options that will override default fixture values
   @param buildType 'build' or 'make'
   @returns {Object}
   */
  buildRawList({
    name,
    number,
    opts,
    buildType = 'build'
  } = {}) {
    let definition = FactoryGuy.lookupDefinitionForFixtureName(name, true);
    if (number >= 0) {
      let parts = FactoryGuy.extractArgumentsShort(...opts);
      return definition.buildList(name, number, parts.traits, parts.opts, buildType);
    }
    return opts.map(function (innerArgs) {
      if (typeOf(innerArgs) !== 'array') {
        innerArgs = [innerArgs];
      }
      let parts = FactoryGuy.extractArgumentsShort(...innerArgs);
      return definition.build(name, parts.opts, parts.traits, buildType);
    });
  }

  /**
   Creates object with model attributes and relationships combined
   based on your traits and options
    @param {String} name  fixture name
   @param {String} trait  optional trait names ( one or more )
   @param {Object} options  optional fixture options that will override default fixture values
   @returns {Object} object with attributes and relationships combined
   */
  attributesFor(...originalArgs) {
    this.ensureStore();
    let args = FactoryGuy.extractArguments(...originalArgs),
      definition = FactoryGuy.lookupDefinitionForFixtureName(args.name, true),
      {
        modelName
      } = definition,
      fixture = this.buildRaw(Object.assign(args, {
        buildType: 'make'
      }));
    let data = this.fixtureBuilder(modelName).convertForMake(modelName, fixture);
    return data.data.attributes;
  }

  /**
   Make new model and save to store.
    @param {String} name  fixture name
   @param {String} trait  optional trait names ( one or more )
   @param {Object} options  optional fixture options that will override default fixture values
   @returns {DS.Model} record
   */
  make(...originalArgs) {
    this.ensureStore();
    let args = FactoryGuy.extractArguments(...originalArgs),
      definition = FactoryGuy.lookupDefinitionForFixtureName(args.name, true),
      {
        modelName
      } = definition,
      fixture = this.buildRaw(Object.assign(args, {
        buildType: 'make'
      }));
    let data = this.fixtureBuilder(modelName).convertForMake(modelName, fixture),
      model = this.store.push(data);
    if (definition.hasAfterMake()) {
      definition.applyAfterMake(model, args.opts);
    }
    return model;
  }

  /**
   Make new model.
    @param {String} name  fixture name
   @param {String} trait  optional trait names ( one or more )
   @param {Object} options  optional fixture options that will override default fixture values
   @returns {DS.Model} record
   */
  makeNew(...originalArgs) {
    this.ensureStore();
    let args = FactoryGuy.extractArguments(...originalArgs),
      modelName = FactoryGuy.lookupModelForFixtureName(args.name, true),
      fixture = this.buildRaw(Object.assign(args, {
        buildType: 'make'
      }));
    delete fixture.id;
    return this.store.createRecord(modelName, fixture);
  }

  /**
   Make a list of model instances
    ```
   FactoryGuy.makeList('bob') // makes 0 bob's
    FactoryGuy.makeList('bob', 2) // makes 2 bob's
    FactoryGuy.makeList('bob', 2, 'with_car' , {name: "Dude"})
   // makes 2 bob's that have 'with_car' trait and name of "Dude"
    FactoryGuy.makeList('bob', 'with_car', ['with_car',{name: "Dude"}])
   // 2 User model with bob attributes, where the first also has 'with_car' trait
   // the last has 'with_car' trait and name of "Dude"
   ```
    @param {String} name name of fixture
   @param {Number} number optional number of models to build
   @param {String} trait  optional trait names ( one or more )
   @param {Object} options  optional fixture options that will override default fixture values
   @returns {Array} list of json fixtures or records depending on the adapter type
   */
  makeList(...args) {
    this.ensureStore();
    this.ensureNameInArguments('makeList', args);
    let {
      name,
      number,
      opts
    } = FactoryGuy.extractListArguments(...args);
    this.ensureNameIsValid(name);
    if (number >= 0) {
      return Array(number).fill().map(() => this.make(...[name, ...opts]));
    }
    return opts.map(innerArgs => {
      if (typeOf(innerArgs) !== 'array') {
        innerArgs = [innerArgs];
      }
      return this.make(...[name, ...innerArgs]);
    });
  }
  ensureNameInArguments(method, args) {
    assert(`[ember-data-factory-guy] ${method} needs at least a name
      ( of model or named factory definition )`, args.length > 0);
  }
  ensureStore() {
    assert(`[ember-data-factory-guy] FactoryGuy does not have the application's store.
       Use setupFactoryGuy(this) in model/component test
       before using make/makeList`, this.store);
  }
  ensureNameIsValid(name) {
    FactoryGuy.lookupDefinitionForFixtureName(name, true);
  }
  reset() {
    this.store = null;
    this.requestManager?.stop();
    this.requestManager = null;
    this.fixtureBuilderFactory = null;
    this.resetDefinitions();
  }

  /**
   Reset the id sequence for the models back to zero.
   */
  resetDefinitions() {
    for (let model in modelDefinitions) {
      let definition = modelDefinitions[model];
      definition.reset();
    }
  }

  /**
   Build url's for the mockjax calls. Proxy to the adapters buildURL method.
    @param {String} modelName model type name like 'user' for User model
   @param {String} id
   @param {String} snapshot usually null, but passing adapterOptions for GET requests
   @return {String} requestType string like 'findRecord', 'queryRecord'
   @return {String} queryParams optional
   */
  buildURL(modelName, id = null, snapshot, requestType, queryParams) {
    const adapter = this.store.adapterFor(modelName);
    const clonedQueryParams = Object.assign({}, queryParams);
    // some adapters can modify the query params so use a copy
    // so as not to modify the internal stored params
    // which are important later
    return adapter.buildURL(modelName, id, snapshot, requestType, clonedQueryParams);
  }

  /**
   Change reload behavior to only used cached models for find/findAll.
   You still have to handle query calls, since they always ajax for data.
    @params {Array} except list of models you don't want to mark as cached
   */
  cacheOnlyMode({
    except = []
  } = {}) {
    let store = this.store;
    let findAdapter = store.adapterFor.bind(store);
    store.adapterFor = function (name) {
      let adapter = findAdapter(name);
      let shouldCache = () => {
        if (isPresent(except)) {
          return A(except).includes(name);
        }
        return false;
      };
      adapter.shouldBackgroundReloadAll = shouldCache;
      adapter.shouldBackgroundReloadRecord = shouldCache;
      adapter.shouldReloadRecord = shouldCache;
      adapter.shouldReloadAll = shouldCache;
      return adapter;
    };
  }

  /**
   extract list arguments from makeList, buildList where the name should be first,
   and optionally a number next, or a list of traits and or options like:
    ['users', 2]  => {name: 'users', number: 2}
   ['users', 2, 'trait']  => {name: 'users', opts: ['trait']}
   ['users', 2, 'trait1', 'trait2' ] => {name: 'users', number: 2, opts: ['trait1', 'trait2']}
   ['users', 'trait1', 'trait2' ] =>
   {name: 'users', number: undefined, opts: ['trait1', 'trait2']}
   ['users', 'trait1', 'trait2', {name: 'Bob'} ] =>
   {name: 'users', number: undefined, opts: ['trait1', 'trait2', {name: 'Bob'}]}
    @param args
   @returns {{name: *, number: (*|number), opts: *[]}}
   */
  static extractListArguments(...args) {
    args = args.slice();
    let name = args.shift(),
      number = args[0] || 0;
    if (typeof number === 'number') {
      args.shift();
    } else {
      number = undefined;
    }
    return {
      name,
      number,
      opts: args
    };
  }

  /**
   extract arguments for build and make function
    @param {String} name  fixture name
   @param {String} trait  optional trait names ( one or more )
   @param {Object} opts  optional fixture options that will override default fixture values
   @returns {Object} json fixture
   */
  static extractArguments(...args) {
    args = args.slice();
    let name = args.shift();
    if (!name) {
      throw new Error('[ember-data-factory-guy] build/make needs a factory name to build');
    }
    return Object.assign({
      name
    }, FactoryGuy.extractArgumentsShort(...args));
  }
  static extractArgumentsShort(...args) {
    args = args.slice();
    let opts = {};
    if (typeOf(args[args.length - 1]) === 'object') {
      opts = args.pop();
    }
    // whatever is left are traits
    let traits = A(args).compact();
    return {
      opts,
      traits
    };
  }

  /**
    @param {String} name a fixture name could be model name like 'person'
   or a named person in model definition like 'dude'
   @param {Boolean} assertItExists true if you want to throw assertion if no definition found
   @returns {ModelDefinition} ModelDefinition associated with model or undefined if not found
   */
  static lookupDefinitionForFixtureName(name, assertItExists = false) {
    let definition;
    for (let model in modelDefinitions) {
      definition = modelDefinitions[model];
      if (definition.matchesName(name)) {
        return definition;
      }
    }
    assert(`[ember-data-factory-guy] Can't find that factory named [ ${name} ]`, !definition && assertItExists);
  }

  /**
   Given a fixture name like 'person' or 'dude' determine what model this name
   refers to. In this case it's 'person' for each one.
    @param {String} name  a fixture name could be model name like 'person'
   or a named person in model definition like 'dude'
   @returns {String} model  name associated with fixture name or undefined if not found
   */
  static lookupModelForFixtureName(name, assertItExists = false) {
    let definition = this.lookupDefinitionForFixtureName(name, assertItExists);
    if (definition) {
      return definition.modelName;
    }
  }
}
let factoryGuy = new FactoryGuy(),
  make = factoryGuy.make.bind(factoryGuy),
  makeNew = factoryGuy.makeNew.bind(factoryGuy),
  makeList = factoryGuy.makeList.bind(factoryGuy),
  build = factoryGuy.build.bind(factoryGuy),
  buildList = factoryGuy.buildList.bind(factoryGuy),
  attributesFor = factoryGuy.attributesFor.bind(factoryGuy);

class MockRequest {
  constructor() {
    this.status = 200;
    this.responseHeaders = new Headers();
    this.responseJson = null;
    this.errorResponse = null;
    this.isDisabled = false;
    this.isDestroyed = false;
    this.timesCalled = 0;
  }

  /**
   * Set the adapter options that this mockCreate will be using
   *
   * @param {Object} options adapterOptions
   */
  withAdapterOptions(options) {
    this.adapterOptions = options;
    this.setupHandler();
    return this;
  }

  /**
   */
  get() {}

  /**
   * @returns {String}
   */
  getUrl() {}
  getType() {
    return 'GET';
  }
  returns(/*options = {}*/) {}

  /**
   * headers arg can be a Headers() instance, or an object like { a: 1, b: 2 }
   */
  addResponseHeaders(headers) {
    this.responseHeaders = new Headers([...this.responseHeaders.entries(), ...(headers instanceof Headers ? headers.entries() : Object.entries(headers))]);
  }
  succeeds(opts = {}) {
    this.status = opts.status || 200;
    this.errorResponse = null;
    return this;
  }
  isErrorStatus(status) {
    return !!status.toString().match(/^([345]\d{2})/);
  }
  fails(opts = {}) {
    let convertErrors = Object.prototype.hasOwnProperty.call(opts, 'convertErrors') ? opts.convertErrors : true,
      status = opts.status || 500,
      response = opts.response || null;
    assert(`[ember-data-factory-guy] 'fails' method status code must be 3XX, 4XX or 5XX,
        you are using: ${status}`, this.isErrorStatus(status));
    this.status = status;
    this.errorResponse = response;
    if (response && convertErrors) {
      let errors = this.fixtureBuilder.convertResponseErrors(response, this.status);
      this.errorResponse = errors;
    }
    return this;
  }

  // for a fails response, the response you might have set ( like a payload ) will
  // be discarded in favour of the error response that was built for you in fails method
  actualResponseJson() {
    let responseText = this.isErrorStatus(this.status) ? this.errorResponse : this.responseJson;
    return JSON.stringify(responseText);
  }
  getResponse() {
    return new Response(this.actualResponseJson(), {
      status: this.status,
      headers: this.responseHeaders
    });
  }
  async logInfo() {
    if (factoryGuy.logLevel > 0) {
      let responseBody = null;
      try {
        responseBody = await this.getResponse().clone().text();
      } catch (e) {
        // continue
      }
      try {
        responseBody = await this.getResponse().clone().json();
      } catch (e) {
        // continue
      }
      const name = this.constructor.name.replace('Request', ''),
        type = this.getType(),
        status = `[${this.status}]`,
        url = this.getUrl();
      let fullUrl = url;
      if (!isEmptyObject(this.queryParams)) {
        fullUrl = [url, '?', param(this.queryParams)].join('');
      }
      const info = ['[factory-guy]', name, type, status, fullUrl, responseBody];
      console.log(...info);
    }
  }
  withParams(queryParams) {
    this.queryParams = queryParams;
    return this;
  }
  hasQueryParams() {
    return !isEmptyObject(this.queryParams);
  }
  withSomeParams(someQueryParams) {
    this.someQueryParams = someQueryParams;
    return this;
  }

  // eslint-disable-next-line no-unused-vars
  paramsMatch({
    request,
    params
  }) {
    const requestUrl = new URL(request.url, window.location.origin);
    if (!isEmptyObject(this.someQueryParams)) {
      // request must have every specified item in someQueryParams, but can have more.
      const someQueryParams = new URLSearchParams(param(this.someQueryParams)); // convert json -> URLSearchParams
      return someQueryParams.keys().every(key => isEqual(someQueryParams.getAll(key), requestUrl.searchParams.getAll(key)));
    }
    if (!isEmptyObject(this.queryParams)) {
      // request must have exactly specified queryParams, no more no less.
      const queryParams = new URLSearchParams(param(this.queryParams));
      return queryParams.keys().every(key => isEqual(queryParams.getAll(key), requestUrl.searchParams.getAll(key))) && requestUrl.searchParams.keys().every(key => isEqual(queryParams.getAll(key), requestUrl.searchParams.getAll(key)));
    }
    return true;
  }
  attributesMatch(requestBody, matchParams) {
    return isMatch(requestBody, matchParams);
  }
  async getRequestBody(request) {
    // Try to figure out request body type without relying on request headers
    try {
      return await request.clone().json();
    } catch (e) {
      // continue
    }
    try {
      return await request.clone().formData();
    } catch (e) {
      // continue
    }
    try {
      return await request.clone().text();
    } catch (e) {
      // continue
    }
  }
  async extraRequestMatches({
    request,
    params
  }) {
    if (this.matchArgs) {
      const requestBody = await this.getRequestBody(request);
      if (typeof this.matchArgs === 'function') {
        return this.matchArgs(requestBody);
      } else {
        return this.attributesMatch(requestBody, this.matchArgs);
      }
    }
    return await this.paramsMatch({
      request,
      params
    });
  }
  async matches({
    request,
    params
  }) {
    if (this.isDisabled) {
      return false;
    }
    const isMatch = await this.extraRequestMatches({
      request,
      params
    });
    if (!isMatch) {
      return false;
    }
    this.timesCalled++;
    await this.logInfo();
    if (this.useOnce) {
      this.disable();
    }
    return true;
  }
  match(matches) {
    this.matchArgs = matches;
    return this;
  }

  // mockId holds the url for this mock request
  oldUrl() {
    return this.mockId && this.mockId.url;
  }
  changedUrl() {
    return this.getUrl() !== this.oldUrl();
  }
  setupHandler() {
    if (!this.mockId) {
      factoryGuy.requestManager.addHandler(this);
    } else if (this.changedUrl()) {
      factoryGuy.requestManager.replaceHandler(this);
    }
  }

  // once the mock is used, it will disable itself, so it can't be used again.
  // most useful when using mockCreate to make the same type of model
  // over and over again, and the returning id is different.
  singleUse() {
    this.useOnce = true;
  }
  disable() {
    this.isDisabled = true;
  }
  enable() {
    this.isDisabled = false;
  }
  destroy() {
    factoryGuy.requestManager.removeHandler(this);
    this.isDestroyed = true;
  }
}

/* Disabling the following lint rules as `MockStoreRequest` and `MockGetRequest` contains a `this.get` method */
/* eslint-disable ember/no-get, ember/classic-decorator-no-classic-methods */
class MockStoreRequest extends MockRequest {
  constructor(modelName, requestType) {
    super();
    this.modelName = modelName;
    this.requestType = requestType;
    this.fixtureBuilder = factoryGuy.fixtureBuilder(this.modelName);
  }

  /**
   Used by getUrl to => this.get('id')
    MockGetRequest overrides this since those mocks have a payload with the id
    For mockDelete: If the id is null the url will not include the id, and
   can therefore be used to match any delete for this modelName
   */
  get(...args) {
    if (args[0] === 'id') {
      return this.id;
    }
  }

  /**
   * Using adapterOptions for snapshot in GET requests
   *
   * @returns {String}
   */
  getUrl() {
    return factoryGuy.buildURL(this.modelName, this.get('id'), this.makeFakeSnapshot(), this.requestType, this.queryParams);
  }

  /**
   * Create fake snaphot with adapterOptions and record
   *
   * @returns {{adapterOptions: (*|Object), record: (*|DS.Model)}}
   */
  makeFakeSnapshot() {
    let record = this.model;
    if (!record && this.get('id')) {
      record = factoryGuy.store.peekRecord(this.modelName, this.get('id'));
    }
    return {
      adapterOptions: this.adapterOptions,
      record
    };
  }

  /**
   This is tricky, but the main idea here is:
    #1 Take the keys they want to match and transform them to what the serialized
   version would be ie. company => company_id
   #2 Take the matchArgs and turn them into a FactoryGuy payload class by
   FactoryGuy.build(ing) them into a payload
   #3 Wrap the request data into a FactoryGuy payload class
   #4 Go though the keys from #1 and check that both the payloads from #2/#3 have the
   same values
    @param requestData
   @returns {boolean} true is no attributes to match or they all match
   */
  attributesMatch(requestData, matchParams) {
    if (isEmptyObject(matchParams)) {
      return true;
    }
    let builder = factoryGuy.fixtureBuilder(this.modelName);

    // transform they match keys
    let matchCheckKeys = Object.keys(matchParams).map(key => {
      return builder.transformKey(this.modelName, key);
    });
    // build the match args into a JSONPayload class
    let buildOpts = {
      serializeMode: true,
      transformKeys: true
    };
    let expectedData = builder.convertForBuild(this.modelName, matchParams, buildOpts);

    // wrap request data in a JSONPayload class
    builder.wrapPayload(this.modelName, requestData);

    // success if all values match
    return matchCheckKeys.every(key => {
      return isEquivalent(expectedData.get(key), requestData.get(key));
    });
  }
}

export { ActiveModelFixtureBuilder as A, FixtureBuilderFactory as F, JSONFixtureBuilder as J, MockStoreRequest as M, RequestManager as R, _defineProperty as _, isEquivalent as a, parseUrl as b, MockRequest as c, makeNew as d, makeList as e, factoryGuy as f, build as g, buildList as h, isEmptyObject as i, attributesFor as j, getTypeUrl as k, assignMockId as l, make as m, getKey as n, interceptor as o, param as p, RESTFixtureBuilder as q, JSONAPIFixtureBuilder as r, MissingSequenceError as s, verifyId as v };
//# sourceMappingURL=mock-store-request-ChvsosiF.js.map