diff --git a/packages/-ember-data/tests/integration/identifiers/scenarios-test.ts b/packages/-ember-data/tests/integration/identifiers/scenarios-test.ts index 45526ace9c7..07337492994 100644 --- a/packages/-ember-data/tests/integration/identifiers/scenarios-test.ts +++ b/packages/-ember-data/tests/integration/identifiers/scenarios-test.ts @@ -13,7 +13,7 @@ import Adapter from '@ember-data/adapter'; import Serializer from '@ember-data/serializer'; import { resolve, all } from 'rsvp'; import { ExistingResourceObject } from '@ember-data/store/-private/ts-interfaces/ember-data-json-api'; -import { Dict } from '@ember-data/store/-private/ts-interfaces/utils'; +import { ConfidentDict } from '@ember-data/store/-private/ts-interfaces/utils'; import { StableRecordIdentifier } from '@ember-data/store/-private/ts-interfaces/identifier'; import { identifierCacheFor } from '@ember-data/store/-private'; import { set } from '@ember/object'; @@ -30,8 +30,8 @@ if (IDENTIFIERS) { let store; let calls; let secondaryCache: { - id: Dict<string, string>; - username: Dict<string, string>; + id: ConfidentDict<string>; + username: ConfidentDict<string>; }; class TestSerializer extends Serializer { normalizeResponse(_, __, payload) { @@ -232,7 +232,7 @@ if (IDENTIFIERS) { module('Secondary Cache using an attribute as an alternate id', function(hooks) { let store; let calls; - let secondaryCache: Dict<string, string>; + let secondaryCache: ConfidentDict<string>; class TestSerializer extends Serializer { normalizeResponse(_, __, payload) { return payload; diff --git a/packages/adapter/addon/-private/index.js b/packages/adapter/addon/-private/index.js index e244a107c58..4d1d18fb8df 100644 --- a/packages/adapter/addon/-private/index.js +++ b/packages/adapter/addon/-private/index.js @@ -7,3 +7,4 @@ export { determineBodyPromise } from './utils/determine-body-promise'; export { serializeQueryParams } from './utils/serialize-query-params'; export { default as fetch } from './utils/fetch'; export { default as BuildURLMixin } from './build-url-mixin'; +export { default as serializeIntoHash } from './utils/serialize-into-hash'; diff --git a/packages/adapter/addon/-private/utils/serialize-into-hash.js b/packages/adapter/addon/-private/utils/serialize-into-hash.js new file mode 100644 index 00000000000..daa20881f70 --- /dev/null +++ b/packages/adapter/addon/-private/utils/serialize-into-hash.js @@ -0,0 +1,11 @@ +export default function serializeIntoHash(store, modelClass, snapshot, options = { includeId: true }) { + const serializer = store.serializerFor(modelClass.modelName); + + if (typeof serializer.serializeIntoHash === 'function') { + const data = {}; + serializer.serializeIntoHash(data, modelClass, snapshot, options); + return data; + } + + return serializer.serialize(snapshot, options); +} diff --git a/packages/adapter/addon/json-api.js b/packages/adapter/addon/json-api.js index e473453a413..dc3b2bfa820 100644 --- a/packages/adapter/addon/json-api.js +++ b/packages/adapter/addon/json-api.js @@ -4,6 +4,7 @@ import { dasherize } from '@ember/string'; import RESTAdapter from './rest'; import { pluralize } from 'ember-inflector'; +import { serializeIntoHash } from './-private'; /** The `JSONAPIAdapter` is the default adapter used by Ember Data. It @@ -227,12 +228,8 @@ const JSONAPIAdapter = RESTAdapter.extend({ return pluralize(dasherized); }, - // TODO: Remove this once we have a better way to override HTTP verbs. updateRecord(store, type, snapshot) { - let data = {}; - let serializer = store.serializerFor(type.modelName); - - serializer.serializeIntoHash(data, type, snapshot, { includeId: true }); + const data = serializeIntoHash(store, type, snapshot); let url = this.buildURL(type.modelName, snapshot.id, snapshot, 'updateRecord'); diff --git a/packages/adapter/addon/rest.js b/packages/adapter/addon/rest.js index 763f56a8234..f0f9c86f671 100644 --- a/packages/adapter/addon/rest.js +++ b/packages/adapter/addon/rest.js @@ -23,6 +23,7 @@ import AdapterError, { } from '@ember-data/adapter/error'; import { warn } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; +import { serializeIntoHash } from './-private'; const Promise = EmberPromise; const hasJQuery = typeof jQuery !== 'undefined'; @@ -723,13 +724,11 @@ const RESTAdapter = Adapter.extend(BuildURLMixin, { @return {Promise} promise */ createRecord(store, type, snapshot) { - let data = {}; - let serializer = store.serializerFor(type.modelName); let url = this.buildURL(type.modelName, null, snapshot, 'createRecord'); - serializer.serializeIntoHash(data, type, snapshot, { includeId: true }); + const data = serializeIntoHash(store, type, snapshot); - return this.ajax(url, 'POST', { data: data }); + return this.ajax(url, 'POST', { data }); }, /** @@ -749,15 +748,12 @@ const RESTAdapter = Adapter.extend(BuildURLMixin, { @return {Promise} promise */ updateRecord(store, type, snapshot) { - let data = {}; - let serializer = store.serializerFor(type.modelName); - - serializer.serializeIntoHash(data, type, snapshot); + const data = serializeIntoHash(store, type, snapshot, {}); let id = snapshot.id; let url = this.buildURL(type.modelName, id, snapshot, 'updateRecord'); - return this.ajax(url, 'PUT', { data: data }); + return this.ajax(url, 'PUT', { data }); }, /** diff --git a/packages/store/addon/-private/identifiers/cache.ts b/packages/store/addon/-private/identifiers/cache.ts index 3764a3e876f..d25ef6ea242 100644 --- a/packages/store/addon/-private/identifiers/cache.ts +++ b/packages/store/addon/-private/identifiers/cache.ts @@ -1,6 +1,6 @@ import { DEBUG } from '@glimmer/env'; import { warn } from '@ember/debug'; -import { Dict } from '../ts-interfaces/utils'; +import { ConfidentDict } from '../ts-interfaces/utils'; import { ResourceIdentifierObject, ExistingResourceObject } from '../ts-interfaces/ember-data-json-api'; import { StableRecordIdentifier, @@ -30,8 +30,8 @@ interface KeyOptions { _allIdentifiers: StableRecordIdentifier[]; } -type IdentifierMap = Dict<string, StableRecordIdentifier>; -type TypeMap = Dict<string, KeyOptions>; +type IdentifierMap = ConfidentDict<StableRecordIdentifier>; +type TypeMap = ConfidentDict<KeyOptions>; export type MergeMethod = ( targetIdentifier: StableRecordIdentifier, matchedIdentifier: StableRecordIdentifier, diff --git a/packages/store/addon/-private/system/fetch-manager.ts b/packages/store/addon/-private/system/fetch-manager.ts index 7551acc481a..d13f730eb92 100644 --- a/packages/store/addon/-private/system/fetch-manager.ts +++ b/packages/store/addon/-private/system/fetch-manager.ts @@ -9,7 +9,6 @@ import { serializerForAdapter } from './store/serializers'; import { InvalidError } from '@ember-data/adapter/error'; import coerceId from './coerce-id'; import { A } from '@ember/array'; - import { _findHasMany, _findBelongsTo, _findAll, _query, _queryRecord } from './store/finders'; import RequestCache from './request-cache'; import { CollectionResourceDocument, SingleResourceDocument } from '../ts-interfaces/ember-data-json-api'; @@ -135,7 +134,14 @@ export default class FetchManager { }, function(error) { if (error instanceof InvalidError) { - let parsedErrors = serializer.extractErrors(store, modelClass, error, snapshot.id); + let parsedErrors = error.errors; + + if (typeof serializer.extractErrors === 'function') { + parsedErrors = serializer.extractErrors(store, modelClass, error, snapshot.id); + } else { + parsedErrors = errorsArrayToHash(error.errors); + } + throw { error, parsedErrors }; } else { throw { error }; diff --git a/packages/store/addon/-private/system/identity-map.ts b/packages/store/addon/-private/system/identity-map.ts index f689f7e5847..01063192552 100644 --- a/packages/store/addon/-private/system/identity-map.ts +++ b/packages/store/addon/-private/system/identity-map.ts @@ -1,5 +1,5 @@ import InternalModelMap from './internal-model-map'; -import { Dict } from '../ts-interfaces/utils'; +import { ConfidentDict } from '../ts-interfaces/utils'; /** @module @ember-data/store @@ -13,7 +13,7 @@ import { Dict } from '../ts-interfaces/utils'; @private */ export default class IdentityMap { - private _map: Dict<string, InternalModelMap> = Object.create(null); + private _map: ConfidentDict<InternalModelMap> = Object.create(null); /** Retrieves the `InternalModelMap` for a given modelName, diff --git a/packages/store/addon/-private/system/internal-model-map.ts b/packages/store/addon/-private/system/internal-model-map.ts index 85c2a4e0006..3d0c48dfcf6 100644 --- a/packages/store/addon/-private/system/internal-model-map.ts +++ b/packages/store/addon/-private/system/internal-model-map.ts @@ -1,6 +1,6 @@ import { assert } from '@ember/debug'; import InternalModel from './model/internal-model'; -import { Dict } from '../ts-interfaces/utils'; +import { ConfidentDict } from '../ts-interfaces/utils'; /** @module @ember-data/store @@ -17,9 +17,9 @@ import { Dict } from '../ts-interfaces/utils'; @private */ export default class InternalModelMap { - private _idToModel: Dict<string, InternalModel> = Object.create(null); + private _idToModel: ConfidentDict<InternalModel> = Object.create(null); private _models: InternalModel[] = []; - private _metadata: Dict<string, any> | null = null; + private _metadata: ConfidentDict<any> | null = null; constructor(public modelName: string) {} @@ -104,7 +104,7 @@ export default class InternalModelMap { * @property metadata * @type Object */ - get metadata(): Dict<string, any> { + get metadata(): ConfidentDict<any> { return this._metadata || (this._metadata = Object.create(null)); } diff --git a/packages/store/addon/-private/system/model/internal-model.ts b/packages/store/addon/-private/system/model/internal-model.ts index b6414a9ecef..2f305bd8d31 100644 --- a/packages/store/addon/-private/system/model/internal-model.ts +++ b/packages/store/addon/-private/system/model/internal-model.ts @@ -23,7 +23,7 @@ import { default as recordDataFor, relationshipStateFor } from '../record-data-f import RecordData from '../../ts-interfaces/record-data'; import { JsonApiResource, JsonApiValidationError } from '../../ts-interfaces/record-data-json-api'; import { Record } from '../../ts-interfaces/record'; -import { Dict } from '../../ts-interfaces/utils'; +import { ConfidentDict } from '../../ts-interfaces/utils'; import { IDENTIFIERS, RECORD_DATA_ERRORS, RECORD_DATA_STATE, REQUEST_SERVICE } from '@ember-data/canary-features'; import { identifierCacheFor } from '../../identifiers/cache'; import { StableRecordIdentifier } from '../../ts-interfaces/identifier'; @@ -107,14 +107,14 @@ export default class InternalModel { __recordArrays: any; _references: any; _recordReference: any; - _manyArrayCache: Dict<string, ManyArray> = Object.create(null); + _manyArrayCache: ConfidentDict<ManyArray> = Object.create(null); // The previous ManyArrays for this relationship which will be destroyed when // we create a new ManyArray, but in the interim the retained version will be // updated if inverse internal models are unloaded. - _retainedManyArrayCache: Dict<string, ManyArray> = Object.create(null); - _relationshipPromisesCache: Dict<string, RSVP.Promise<any>> = Object.create(null); - _relationshipProxyCache: Dict<string, PromiseManyArray | PromiseBelongsTo> = Object.create(null); + _retainedManyArrayCache: ConfidentDict<ManyArray> = Object.create(null); + _relationshipPromisesCache: ConfidentDict<RSVP.Promise<any>> = Object.create(null); + _relationshipProxyCache: ConfidentDict<PromiseManyArray | PromiseBelongsTo> = Object.create(null); currentState: any; error: any; diff --git a/packages/store/addon/-private/system/store.ts b/packages/store/addon/-private/system/store.ts index 3c925be573b..faf07187d8e 100644 --- a/packages/store/addon/-private/system/store.ts +++ b/packages/store/addon/-private/system/store.ts @@ -61,7 +61,6 @@ import hasValidId from '../utils/has-valid-id'; import { RequestPromise } from './request-cache'; import { PromiseProxy } from '../ts-interfaces/promise-proxies'; import { DSModel } from '../ts-interfaces/ds-model'; - const emberRun = emberRunLoop.backburner; const { ENV } = Ember; @@ -3298,7 +3297,14 @@ function _commit(adapter, store, operation, snapshot) { }, function(error) { if (error instanceof InvalidError) { - let parsedErrors = serializer.extractErrors(store, modelClass, error, snapshot.id); + let parsedErrors; + + if (typeof serializer.extractErrors === 'function') { + parsedErrors = serializer.extractErrors(store, modelClass, error, snapshot.id); + } else { + parsedErrors = errorsArrayToHash(error.errors); + } + store.recordWasInvalid(internalModel, parsedErrors, error); } else { store.recordWasError(internalModel, error); diff --git a/packages/store/addon/-private/ts-interfaces/ember-data-json-api.ts b/packages/store/addon/-private/ts-interfaces/ember-data-json-api.ts index d5c39f50e9e..7fadb3b0937 100644 --- a/packages/store/addon/-private/ts-interfaces/ember-data-json-api.ts +++ b/packages/store/addon/-private/ts-interfaces/ember-data-json-api.ts @@ -5,7 +5,7 @@ import { Dict } from './utils'; @module @ember-data/store */ -export type Meta = Dict<string, JSONValue>; +export type Meta = Dict<JSONValue>; /** * Serves as a reference to a `Resource` but does not contain @@ -74,15 +74,15 @@ export type ResourceIdentifierObject = ExistingResourceIdentifierObject | NewRes * Contains the data for an existing resource in JSON:API format */ export interface ExistingResourceObject extends ExistingResourceIdentifierObject { - meta?: Dict<string, JSONValue>; - attributes?: Dict<string, JSONValue>; + meta?: Dict<JSONValue>; + attributes?: Dict<JSONValue>; // these are lossy, need improved typing - relationships?: Dict<string, JSONValue>; - links?: Dict<string, JSONValue>; + relationships?: Dict<JSONValue>; + links?: Dict<JSONValue>; } interface Document { - meta?: Dict<string, JSONValue>; + meta?: Dict<JSONValue>; included?: ExistingResourceObject[]; } diff --git a/packages/store/addon/-private/ts-interfaces/minimum-serializer-interface.ts b/packages/store/addon/-private/ts-interfaces/minimum-serializer-interface.ts new file mode 100644 index 00000000000..f6819d9c2d7 --- /dev/null +++ b/packages/store/addon/-private/ts-interfaces/minimum-serializer-interface.ts @@ -0,0 +1,319 @@ +/** + ## Overview + + In order to properly manage and present your data, `EmberData` + needs to understand the structure of data it receives. + + `Serializers` convert data between the server's API format and + the format `EmberData` understands. + + Data received from an API response is `"normalized"` into + [JSON:API](https://jsonapi.org/) (the format used internally + by `EmberData`), while data sent to an API is `"serialized"` + into the format the API expects. + + ### Implementing a Serializer + + There are only two required serializer methods, one for + normalizing data from the server API format into `JSON:API`, and + another for serializing records via `Snapshot`s into the expected + server API format. + + To implement a serializer, export a class that conforms to the structure + described by the [MinimumSerializerInterface](MinimumSerializerInterface) + from the `app/serializers/` directory. An example is below. + + ```ts + import EmberObject from '@ember/object'; + + export default class ApplicationSerializer extends EmberObject { + normalizeResponse(store, schema, rawPayload) { + return rawPayload; + } + + serialize(snapshot, options) { + const serializedResource = { + id: snapshot.id(), + type: snapshot.modelName, + attributes: snapshot.attributes() + }; + + return serializedResource; + } + } + ``` + + + #### Serializer Resolution + + `store.serializerFor(name)` will lookup serializers defined in + `app/serializers` and return an instance. If no serializer is found, an + error will be thrown. + + `serializerFor` first attempts to find a serializer with an exact match on `name`, + then falls back to checking for the presence of a serializer named `application`. + + ```ts + store.serializerFor('author'); + + // lookup paths (in order) => + // app/serializers/author.js + // app/serializers/application.js + ``` + + Most requests in `ember-data` are made with respect to a particular `type` (or `modelName`) + (e.g., "get me the full collection of **books**" or "get me the **employee** whose id is 37"). We + refer to this as the *"primary"* resource `type`. + + Typically `serializerFor` will be used to find a serializer with a name matching that of the primary + resource `type` for the request, falling back to the `application` serializer for those types that + do not have a defined serializer. This is often described as a `per-model` or `per-type` strategy + for defining serializers. However, because APIs rarely format payloads per-type but rather + per-API-version, this may not be a desired strategy. + + It is recommended that applications define only a single `application` adapter and serializer + where possible. + + If you have multiple API formats and the per-type strategy is not viable, one strategy is to + write an `application` adapter and serializer that make use of `options` to specify the desired + format when making a request. + + ### Using a Serializer + + Any serializer in `app/serializers` can be looked up by `name` using `store.serializerFor(name)`. + + ### Default Serializers + + For applications whose APIs are *very close to* or *exactly* the `REST` or `JSON:API` + format the `@ember-data/serializer` package contains implementations these applications can + extend. It also contains a simple `JSONSerializer` for serializing to/from very basic JSON objects. + + Many applications will find writing their own serializer to be more performant and less + complex than extending these classes even when their API format is very close to that expected + by these serializers. + + It is recommended that apps write their own serializer to best suit the needs of their API and + application. + + @module @ember-data/serializer + @main @ember-data/serializer + @class MinimumSerializerInterface + @public +*/ + +import { Object as JSONObject } from 'json-typescript'; +import Store from '../system/core-store'; +import { JsonApiDocument, SingleResourceDocument } from './ember-data-json-api'; +import Snapshot from '../system/snapshot'; +import ShimModelClass from '../system/model/shim-model-class'; +import { Dict } from './utils'; + +type OptionsHash = Dict<any>; + +interface Serializer { + /** + * This method is responsible for normalizing the value resolved from the promise returned + * by an Adapter request into the format expected by the `Store`. + * + * The output should be a [JSON:API Document](https://jsonapi.org/format/#document-structure) + * with the following additional restrictions: + * + * - `type` should be formatted in the `singular` `dasherized` `lowercase` form + * - `members` (the property names of attributes and relationships) should be formatted + * to match their definition in the corresponding `Model` definition. Typically this + * will be `camelCase`. + * - [`lid`](https://github.com/emberjs/rfcs/blob/master/text/0403-ember-data-identifiers.md) is + * a valid optional sibling to `id` and `type` in both [Resources](https://jsonapi.org/format/#document-resource-objects) + * and [Resource Identifier Objects](https://jsonapi.org/format/#document-resource-identifier-objects) + * + * @method normalizeResponse + * @public + * @param {Store} store - the store service that initiated the request being normalized + * @param {ShimModelClass} schema - An object with methods for accessing information about + * the type, attributes and relationships of the primary type associated with the request. + * @param {JSONObject} rawPayload - The raw JSON response data returned from an API request. + * This correlates to the value the promise returned by the adapter method that performed + * the request resolved to. + * @param {string|null} id - For a `findRecord` request, this is the `id` initially provided + * in the call to `store.findRecord`. Else this value is `null`. + * @param {'findRecord' | 'queryRecord' | 'findAll' | 'findBelongsTo' | 'findHasMany' | 'findMany' | 'query' | 'createRecord' | 'deleteRecord' | 'updateRecord'} requestType - The + * type of request the Adapter had been asked to perform. + * + * @returns {JsonApiDocument} - a document following the structure of a [JSON:API Document](https://jsonapi.org/format/#document-structure). + */ + normalizeResponse( + store: Store, + schema: ShimModelClass, + rawPayload: JSONObject, + id: string | null, + requestType: + | 'findRecord' + | 'queryRecord' + | 'findAll' + | 'findBelongsTo' + | 'findHasMany' + | 'findMany' + | 'query' + | 'createRecord' + | 'deleteRecord' + | 'updateRecord' + ): JsonApiDocument; + + /** + * This method is responsible for serializing an individual record + * via a [Snapshot](Snapshot) into the format expected by the API. + * + * This method is called by `snapshot.serialize()`. + * + * When using `Model`, this method is called by `record.serialize()`. + * + * When using `JSONAPIAdapter` or `RESTAdapter` this method is called + * by `updateRecord` and `createRecord` if `Serializer.serializeIntoHash` + * is not implemented. + * + * @method serialize + * @public + * @param {Snapshot} snapshot - A Snapshot for the record to serialize + * @param {object} [options] + */ + serialize(snapshot: Snapshot, options?: OptionsHash): JSONObject; + + /** + * This method is intended to normalize data into a [JSON:API Document](https://jsonapi.org/format/#document-structure) + * with a data member containing a single [Resource](https://jsonapi.org/format/#document-resource-objects). + * + * - `type` should be formatted in the `singular` `dasherized` `lowercase` form + * - `members` (the property names of attributes and relationships) should be formatted + * to match their definition in the corresponding `Model` definition. Typically this + * will be `camelCase`. + * - [`lid`](https://github.com/emberjs/rfcs/blob/master/text/0403-ember-data-identifiers.md) is + * a valid optional sibling to `id` and `type` in both [Resources](https://jsonapi.org/format/#document-resource-objects) + * and [Resource Identifier Objects](https://jsonapi.org/format/#document-resource-identifier-objects) + * + * This method is called by the `Store` when `store.normalize(modelName, payload)` is + * called. It is recommended to use `store.serializerFor(modelName).normalizeResponse` + * over `store.normalize`. + * + * This method may be called when also using the `RESTSerializer` + * when `serializer.pushPayload` is called by `store.pushPayload`. + * It is recommended to use `store.push` over `store.pushPayload` after normalizing + * the payload directly. + * + * Example: + * ```js + * function pushPayload(store, modelName, rawPayload) { + * const ModelClass = store.modelFor(modelName); + * const serializer = store.serializerFor(modelName); + * const jsonApiPayload = serializer.normalizeResponse(store, ModelClass, rawPayload, null, 'query'); + * + * return store.push(jsonApiPayload); + * } + * ``` + * + * This method may be called when also using the `JSONAPISerializer` + * when normalizing included records. If mixing serializer usage in this way + * we recommend implementing this method, but caution that it may lead + * to unexpected mixing of formats. + * + * This method may also be called when normalizing embedded relationships when + * using the `EmbeddedRecordsMixin`. If using this mixin in a serializer in + * your application we recommend implementing this method, but caution that + * it may lead to unexpected mixing of formats. + * + * @method normalize [OPTIONAL] + * @public + * @optional + * @param {ShimModelClass} schema - An object with methods for accessing information about + * the type, attributes and relationships of the primary type associated with the request. + * @param {JSONObject} rawPayload - Some raw JSON data to be normalized into a [JSON:API Resource](https://jsonapi.org/format/#document-resource-objects). + * @param {string} [prop] - When called by the `EmbeddedRecordsMixin` this param will be the + * property at which the object provided as rawPayload was found. + * @returns {SingleResourceDocument} - A [JSON:API Document](https://jsonapi.org/format/#document-structure) + * containing a single [JSON:API Resource](https://jsonapi.org/format/#document-resource-objects) + * as its primary data. + */ + normalize?(schema: ShimModelClass, rawPayload: JSONObject, prop?: string): SingleResourceDocument; + + /** + * When using `JSONAPIAdapter` or `RESTAdapter` this method is called + * by `adapter.updateRecord` and `adapter.createRecord` if `Serializer.serializeIntoHash` + * is not implemented. + * + * You can use this method to customize the root keys serialized into the payload. + * The hash property should be modified by reference. + * + * For instance, your API may expect resources to be keyed by underscored type in the payload: + * + * ```js + * { + * _user: { + * type: 'user', + * id: '1' + * } + * } + * ``` + * + * Which when using these adapters can be achieved by implementing this method similar + * to the following: + * + * ```js + * serializeIntoHash(hash, ModelClass, snapshot, options) { + * hash[`_${snapshot.modelName}`] = this.serialize(snapshot, options).data; + * } + * ``` + * + * @method serializeIntoHash [OPTIONAL] + * @public + * @optional + * @param hash - a top most object of the request payload onto + * which to append the serialized record + * @param {ShimModelClass} schema - An object with methods for accessing information about + * the type, attributes and relationships of the primary type associated with the request. + * @param {Snapshot} snapshot - A Snapshot for the record to serialize + * @param [options] + * @returns {void} + */ + serializeIntoHash?(hash: object, schema: ShimModelClass, snapshot: Snapshot, options?: OptionsHash): void; + + /** + * This method allows for normalization of data when `store.pushPayload` is called + * and should be implemented if you want to use that method. + * + * The output should be a [JSON:API Document](https://jsonapi.org/format/#document-structure) + * with the following additional restrictions: + * + * - `type` should be formatted in the `singular` `dasherized` `lowercase` form + * - `members` (the property names of attributes and relationships) should be formatted + * to match their definition in the corresponding `Model` definition. Typically this + * will be `camelCase`. + * - [`lid`](https://github.com/emberjs/rfcs/blob/master/text/0403-ember-data-identifiers.md) is + * a valid optional sibling to `id` and `type` in both [Resources](https://jsonapi.org/format/#document-resource-objects) + * and [Resource Identifier Objects](https://jsonapi.org/format/#document-resource-identifier-objects) + * + * If you need better control over normalization or want access to the records being added or updated + * in the store, we recommended using `store.push` over `store.pushPayload` after normalizing + * the payload directly. This can even take advantage of an existing serializer for the format + * the data is in, for example: + * + * ```js + * function pushPayload(store, modelName, rawPayload) { + * const ModelClass = store.modelFor(modelName); + * const serializer = store.serializerFor(modelName); + * const jsonApiPayload = serializer.normalizeResponse(store, ModelClass, rawPayload, null, 'query'); + * + * return store.push(jsonApiPayload); + * } + * ``` + * + * @method pushPayload [OPTIONAL] + * @public + * @optional + * @param {Store} store - the store service that initiated the request being normalized + * @param {JSONObject} rawPayload - The raw JSON response data returned from an API request. + * This JSON should be in the API format expected by the serializer. + * @returns {JsonApiDocument} - a document following the structure of a [JSON:API Document](https://jsonapi.org/format/#document-structure) + */ + pushPayload?(store: Store, rawPayload: JSONObject): JsonApiDocument; +} + +export default Serializer; diff --git a/packages/store/addon/-private/ts-interfaces/utils.ts b/packages/store/addon/-private/ts-interfaces/utils.ts index 3c6af61e96a..43d92465412 100644 --- a/packages/store/addon/-private/ts-interfaces/utils.ts +++ b/packages/store/addon/-private/ts-interfaces/utils.ts @@ -2,4 +2,5 @@ @module @ember-data/store */ -export type Dict<K extends string, V> = { [KK in K]: V }; +export type ConfidentDict<V> = { [key: string]: V }; +export type Dict<V> = { [key: string]: V | undefined };