#

API Docs for: v3.16.0-alpha.2
Show: Inherited Protected Private Deprecated

# File: ../store/addon/-private/system/ds-model-store.ts

    /**
      @module @ember-data/store
    */
    import CoreStore from './core-store';
    import { assert } from '@ember/debug';
    import normalizeModelName from './normalize-model-name';
    import { assign } from '@ember/polyfills';
    import { DEBUG } from '@glimmer/env';
    import { isPresent } from '@ember/utils';
    import { deprecate } from '@ember/application/deprecations';
    import EmberError from '@ember/error';
    import { get } from '@ember/object';
    import ShimModelClass, { getShimClass } from './model/shim-model-class';
    import { setOwner, getOwner } from '@ember/application';
    import { DSModel } from '../ts-interfaces/ds-model';
    import NotificationManager from './record-notification-manager';
    import { StableRecordIdentifier } from '../ts-interfaces/identifier';
    import { DSModelSchemaDefinitionService, getModelFactory } from './schema-definition-service';
    import { CUSTOM_MODEL_CLASS } from '@ember-data/canary-features';
    import RecordDataRecordWrapper from '../ts-interfaces/record-data-record-wrapper';
    import { SchemaDefinitionService } from '../ts-interfaces/schema-definition-service';
    import { RelationshipsSchema } from '../ts-interfaces/record-data-schemas';
    import notifyChanges from './model/notify-changes';
    type DSModelClass = import('@ember-data/model').default;

    /**
      The store service contains all of the data for records loaded from the server.
      It is also responsible for creating instances of `Model` that wrap
      the individual data for a record, so that they can be bound to in your
      Handlebars templates.

      By default, applications will have a single `Store` service that is
      automatically created.

      The store can be customized by extending the service in the following manner:

      ```app/services/store.js
      import Store from '@ember-data/store';

      export default class MyStore extends Store {}
      ```

      You can retrieve models from the store in several ways. To retrieve a record
      for a specific id, use the `Store`'s `findRecord()` method:

      ```javascript
      store.findRecord('person', 123).then(function (person) {
      });
      ```

      By default, the store will talk to your backend using a standard
      REST mechanism. You can customize how the store talks to your
      backend by specifying a custom adapter:

      ```app/adapters/application.js
      import DS from 'ember-data';

      export default Adapter.extend({
      });
      ```

      You can learn more about writing a custom adapter by reading the `Adapter`
      documentation.

      ### Store createRecord() vs. push() vs. pushPayload()

      The store provides multiple ways to create new record objects. They have
      some subtle differences in their use which are detailed below:

      [createRecord](Store/methods/createRecord?anchor=createRecord) is used for creating new
      records on the client side. This will return a new record in the
      `created.uncommitted` state. In order to persist this record to the
      backend, you will need to call `record.save()`.

      [push](Store/methods/push?anchor=push) is used to notify Ember Data's store of new or
      updated records that exist in the backend. This will return a record
      in the `loaded.saved` state. The primary use-case for `store#push` is
      to notify Ember Data about record updates (full or partial) that happen
      outside of the normal adapter methods (for example
      [SSE](http://dev.w3.org/html5/eventsource/) or [Web
      Sockets](http://www.w3.org/TR/2009/WD-websockets-20091222/)).

      [pushPayload](Store/methods/pushPayload?anchor=pushPayload) is a convenience wrapper for
      `store#push` that will deserialize payloads if the
      Serializer implements a `pushPayload` method.

      Note: When creating a new record using any of the above methods
      Ember Data will update `RecordArray`s such as those returned by
      `store#peekAll()` or `store#findAll()`. This means any
      data bindings or computed properties that depend on the RecordArray
      will automatically be synced to include the new or updated record
      values.

      @class Store
      @main @ember-data/store
      @extends Ember.Service
    */

    class Store extends CoreStore {
      public _modelFactoryCache = Object.create(null);
      private _relationshipsDefCache = Object.create(null);
      private _attributesDefCache = Object.create(null);

      instantiateRecord(
        identifier: StableRecordIdentifier,
        createRecordArgs: { [key: string]: any },
        recordDataFor: (identifier: StableRecordIdentifier) => RecordDataRecordWrapper,
        notificationManager: NotificationManager
      ): DSModel {
        let modelName = identifier.type;

        let internalModel = this._internalModelForResource(identifier);
        let createOptions: any = {
          store: this,
          _internalModel: internalModel,
          currentState: internalModel.currentState,
          container: null,
        };
        assign(createOptions, createRecordArgs);

        // ensure that `getOwner(this)` works inside a model instance
        setOwner(createOptions, getOwner(this));

        delete createOptions.container;
        let record = this._modelFactoryFor(modelName).create(createOptions);
        //todo optimize
        notificationManager.subscribe(identifier, (identifier, value) => notifyChanges(identifier, value, record, this));
        return record;
      }

      teardownRecord(record: DSModel) {
        record.destroy();
      }

      /**
      Returns the model class for the particular `modelName`.

      The class of a model might be useful if you want to get a list of all the
      relationship names of the model, see
      [`relationshipNames`](/ember-data/release/classes/Model?anchor=relationshipNames)
      for example.

      @method modelFor
      @param {String} modelName
      @return {Model}
        */
      modelFor(modelName: string): ShimModelClass | DSModelClass {
        if (DEBUG) {
          assertDestroyedStoreOnly(this, 'modelFor');
        }
        assert(`You need to pass a model name to the store's modelFor method`, isPresent(modelName));
        assert(
          `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
          typeof modelName === 'string'
        );

        let maybeFactory = this._modelFactoryFor(modelName);

        // for factorFor factory/class split
        let klass = maybeFactory && maybeFactory.class ? maybeFactory.class : maybeFactory;
        if (!klass || !klass.isModel) {
          if (!CUSTOM_MODEL_CLASS || !this.getSchemaDefinitionService().doesTypeExist(modelName)) {
            throw new EmberError(`No model was found for '${modelName}' and no schema handles the type`);
          }
          return getShimClass(this, modelName);
        } else {
          return klass;
        }
      }

      _modelFactoryFor(modelName: string): DSModelClass {
        if (DEBUG) {
          assertDestroyedStoreOnly(this, '_modelFactoryFor');
        }
        assert(`You need to pass a model name to the store's _modelFactoryFor method`, isPresent(modelName));
        assert(
          `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
          typeof modelName === 'string'
        );
        let normalizedModelName = normalizeModelName(modelName);
        let factory = getModelFactory(this, this._modelFactoryCache, normalizedModelName);

        return factory;
      }

      /*
      Returns whether a ModelClass exists for a given modelName
      This exists for legacy support for the RESTSerializer,
      which due to how it must guess whether a key is a model
      must query for whether a match exists.

      We should investigate an RFC to make this public or removing
      this requirement.

      @private
     */
      _hasModelFor(modelName) {
        if (DEBUG) {
          assertDestroyingStore(this, '_hasModelFor');
        }
        assert(`You need to pass a model name to the store's hasModelFor method`, isPresent(modelName));
        assert(
          `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
          typeof modelName === 'string'
        );

        if (CUSTOM_MODEL_CLASS) {
          return this.getSchemaDefinitionService().doesTypeExist(modelName);
        } else {
          assert(`You need to pass a model name to the store's hasModelFor method`, isPresent(modelName));
          assert(
            `Passing classes to store methods has been removed. Please pass a dasherized string instead of ${modelName}`,
            typeof modelName === 'string'
          );
          let normalizedModelName = normalizeModelName(modelName);
          let factory = getModelFactory(this, this._modelFactoryCache, normalizedModelName);

          return factory !== null;
        }
      }

      _relationshipMetaFor(modelName: string, id: string | null, key: string) {
        if (CUSTOM_MODEL_CLASS) {
          return this._relationshipsDefinitionFor(modelName)[key];
        } else {
          let modelClass = this.modelFor(modelName);
          let relationshipsByName = get(modelClass, 'relationshipsByName');
          return relationshipsByName.get(key);
        }
      }

      _attributesDefinitionFor(modelName: string, identifier?: StableRecordIdentifier) {
        if (CUSTOM_MODEL_CLASS) {
          if (identifier) {
            return this.getSchemaDefinitionService().attributesDefinitionFor(identifier);
          } else {
            return this.getSchemaDefinitionService().attributesDefinitionFor(modelName);
          }
        } else {
          let attributes = this._attributesDefCache[modelName];

          if (attributes === undefined) {
            let modelClass = this.modelFor(modelName);
            let attributeMap = get(modelClass, 'attributes');

            attributes = Object.create(null);
            attributeMap.forEach((meta, name) => (attributes[name] = meta));
            this._attributesDefCache[modelName] = attributes;
          }

          return attributes;
        }
      }

      _relationshipsDefinitionFor(modelName: string, identifier?: StableRecordIdentifier): RelationshipsSchema {
        if (CUSTOM_MODEL_CLASS) {
          if (identifier) {
            return this.getSchemaDefinitionService().relationshipsDefinitionFor(identifier);
          } else {
            return this.getSchemaDefinitionService().relationshipsDefinitionFor(modelName);
          }
        } else {
          let relationships = this._relationshipsDefCache[modelName];

          if (relationships === undefined) {
            let modelClass = this.modelFor(modelName);
            relationships = get(modelClass, 'relationshipsObject') || null;
            this._relationshipsDefCache[modelName] = relationships;
          }

          return relationships;
        }
      }

      getSchemaDefinitionService(): SchemaDefinitionService {
        if (CUSTOM_MODEL_CLASS) {
          if (!this._schemaDefinitionService) {
            this._schemaDefinitionService = new DSModelSchemaDefinitionService(this);
          }
          return this._schemaDefinitionService;
        } else {
          throw 'schema service is only available when custom model class feature flag is on';
        }
      }
    }

    let assertDestroyingStore: Function;
    let assertDestroyedStoreOnly: Function;

    if (DEBUG) {
      assertDestroyingStore = function assertDestroyedStore(store, method) {
        if (!store.shouldAssertMethodCallsOnDestroyedStore) {
          deprecate(
            `Attempted to call store.${method}(), but the store instance has already been destroyed.`,
            !(store.isDestroying || store.isDestroyed),
            {
              id: 'ember-data:method-calls-on-destroyed-store',
              until: '3.8',
            }
          );
        } else {
          assert(
            `Attempted to call store.${method}(), but the store instance has already been destroyed.`,
            !(store.isDestroying || store.isDestroyed)
          );
        }
      };
      assertDestroyedStoreOnly = function assertDestroyedStoreOnly(store, method) {
        if (!store.shouldAssertMethodCallsOnDestroyedStore) {
          deprecate(
            `Attempted to call store.${method}(), but the store instance has already been destroyed.`,
            !store.isDestroyed,
            {
              id: 'ember-data:method-calls-on-destroyed-store',
              until: '3.8',
            }
          );
        } else {
          assert(
            `Attempted to call store.${method}(), but the store instance has already been destroyed.`,
            !store.isDestroyed
          );
        }
      };
    }

    export default Store;