API Docs for: 5.4.0-beta.5+07ce8abc
Show:

File: ../packages/store/src/-private/document.ts

/**
 * @module @ember-data/store
 */
import { defineSignal } from '@ember-data/tracking/-private';
import { assert } from '@warp-drive/build-config/macros';
import type { StableDocumentIdentifier } from '@warp-drive/core-types/identifier';
import type { RequestInfo } from '@warp-drive/core-types/request';
import type { Link, Meta, PaginationLinks } from '@warp-drive/core-types/spec/json-api-raw';

import type { Store } from './store-service';

function urlFromLink(link: Link): string {
  if (typeof link === 'string') return link;
  return link.href;
}

/**
 * A Document is a class that wraps the response content from a request to the API
 * returned by `Cache.put` or `Cache.peek`, converting resource-identifiers into
 * record instances.
 *
 * It is not directly instantiated by the user, and its properties should not
 * be directly modified. Whether individual properties are mutable or not is
 * determined by the record instance itself.
 *
 * @public
 * @class Document
 */
export class Document<T> {
  /**
   * The links object for this document, if any
   *
   * e.g.
   *
   * ```
   * {
   *   self: '/articles?page[number]=3',
   * }
   * ```
   *
   * @property links
   * @type {object|undefined} - a links object
   * @public
   */
  declare links?: PaginationLinks;
  /**
   * The primary data for this document, if any.
   *
   * If this document has no primary data (e.g. because it is an error document)
   * this property will be `undefined`.
   *
   * For collections this will be an array of record instances,
   * for single resource requests it will be a single record instance or null.
   *
   * @property data
   * @public
   * @type {object|Array<object>|null|undefined} - a data object
   */
  declare data?: T;

  /**
   * The errors returned by the API for this request, if any
   *
   * @property errors
   * @public
   * @type {object|undefined} - an errors object
   */
  declare errors?: object[];

  /**
   * The meta object for this document, if any
   *
   * @property meta
   * @public
   * @type {object|undefined} - a meta object
   */
  declare meta?: Meta;

  /**
   * The identifier associated with this document, if any
   *
   * @property identifier
   * @public
   * @type {StableDocumentIdentifier|null}
   */
  declare identifier: StableDocumentIdentifier | null;

  #store: Store;
  constructor(store: Store, identifier: StableDocumentIdentifier | null) {
    this.#store = store;
    this.identifier = identifier;
  }

  async #request(
    link: keyof PaginationLinks,
    options: Partial<RequestInfo<T, Document<T>>>
  ): Promise<Document<T> | null> {
    const href = this.links?.[link];
    if (!href) {
      return null;
    }

    options.method = options.method || 'GET';
    Object.assign(options, { url: urlFromLink(href) });
    const response = await this.#store.request<Document<T>>(options);

    return response.content;
  }

  /**
   * Fetches the related link for this document, returning a promise that resolves
   * with the document when the request completes. If no related link is present,
   * will fallback to the self link if present
   *
   * @method fetch
   * @public
   * @param {object} options
   * @return Promise<Document>
   */
  fetch(options: Partial<RequestInfo<T, Document<T>>> = {}): Promise<Document<T>> {
    assert(`No self or related link`, this.links?.related || this.links?.self);
    options.cacheOptions = options.cacheOptions || {};
    options.cacheOptions.key = this.identifier?.lid;
    return this.#request(this.links.related ? 'related' : 'self', options) as Promise<Document<T>>;
  }

  /**
   * Fetches the next link for this document, returning a promise that resolves
   * with the new document when the request completes, or null  if there is no
   * next link.
   *
   * @method next
   * @public
   * @param {object} options
   * @return Promise<Document | null>
   */
  next(options: Partial<RequestInfo<T, Document<T>>> = {}): Promise<Document<T> | null> {
    return this.#request('next', options);
  }

  /**
   * Fetches the prev link for this document, returning a promise that resolves
   * with the new document when the request completes, or null if there is no
   * prev link.
   *
   * @method prev
   * @public
   * @param {object} options
   * @return Promise<Document | null>
   */
  prev(options: Partial<RequestInfo<T, Document<T>>> = {}): Promise<Document<T> | null> {
    return this.#request('prev', options);
  }

  /**
   * Fetches the first link for this document, returning a promise that resolves
   * with the new document when the request completes, or null if there is no
   * first link.
   *
   * @method first
   * @public
   * @param {object} options
   * @return Promise<Document | null>
   */
  first(options: Partial<RequestInfo<T, Document<T>>> = {}): Promise<Document<T> | null> {
    return this.#request('first', options);
  }

  /**
   * Fetches the last link for this document, returning a promise that resolves
   * with the new document when the request completes, or null if there is no
   * last link.
   *
   * @method last
   * @public
   * @param {object} options
   * @return Promise<Document | null>
   */
  last(options: Partial<RequestInfo<T, Document<T>>> = {}): Promise<Document<T> | null> {
    return this.#request('last', options);
  }

  /**
   * Implemented for `JSON.stringify` support.
   *
   * Returns the JSON representation of the document wrapper.
   *
   * This is a shallow serialization, it does not deeply serialize
   * the document's contents, leaving that to the individual record
   * instances to determine how to do, if at all.
   *
   * @method toJSON
   * @public
   * @return
   */
  toJSON(): object {
    const data: Partial<Document<T>> = {};
    data.identifier = this.identifier;
    if (this.data !== undefined) {
      data.data = this.data;
    }
    if (this.links !== undefined) {
      data.links = this.links;
    }
    if (this.errors !== undefined) {
      data.errors = this.errors;
    }
    if (this.meta !== undefined) {
      data.meta = this.meta;
    }
    return data;
  }
}

defineSignal(Document.prototype, 'data');
defineSignal(Document.prototype, 'links');
defineSignal(Document.prototype, 'errors');
defineSignal(Document.prototype, 'meta');