diff --git a/.api-reports/api-report-utilities.api.md b/.api-reports/api-report-utilities.api.md index 60b1fe1d03a..c20f64a4e47 100644 --- a/.api-reports/api-report-utilities.api.md +++ b/.api-reports/api-report-utilities.api.md @@ -87,26 +87,22 @@ type DeepPartialReadonlySet = {} & ReadonlySet>; // @public (undocumented) type DeepPartialSet = {} & Set>; -// @public (undocumented) +// @public export class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "DocumentTransformOptions" needs to be exported by the entry point index.d.ts constructor(transform: TransformFn, options?: DocumentTransformOptions); - // (undocumented) concat(otherTransform: DocumentTransform): DocumentTransform; - // (undocumented) static identity(): DocumentTransform; // @internal @deprecated readonly left?: DocumentTransform; resetCache(): void; // @internal @deprecated readonly right?: DocumentTransform; - // (undocumented) static split(predicate: (document: DocumentNode) => boolean, left: DocumentTransform, right?: DocumentTransform): DocumentTransform & { left: DocumentTransform; right: DocumentTransform; }; - // (undocumented) transformDocument(document: DocumentNode): DocumentNode; } @@ -135,7 +131,7 @@ export interface HKT { return: unknown; } -// @public (undocumented) +// @public export function isFormattedExecutionResult(result?: object): result is FormattedExecutionResult; // @public diff --git a/.api-reports/api-report-utilities_internal.api.md b/.api-reports/api-report-utilities_internal.api.md index 31bd77d7628..f4329853077 100644 --- a/.api-reports/api-report-utilities_internal.api.md +++ b/.api-reports/api-report-utilities_internal.api.md @@ -279,7 +279,7 @@ export const getInMemoryCacheMemoryInternals: (() => { }; }) | undefined; -// @internal @deprecated +// @public export function getMainDefinition(queryDoc: DocumentNode): OperationDefinitionNode | FragmentDefinitionNode; // @internal @deprecated (undocumented) diff --git a/.api-reports/api-report.api.md b/.api-reports/api-report.api.md index 6e6af958af2..29f1a261905 100644 --- a/.api-reports/api-report.api.md +++ b/.api-reports/api-report.api.md @@ -948,26 +948,22 @@ type DistributedRequiredExclude = T extends any ? Required extends Requ export { DocumentNode } -// @public (undocumented) +// @public export class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "DocumentTransformOptions" needs to be exported by the entry point index.d.ts constructor(transform: TransformFn, options?: DocumentTransformOptions); - // (undocumented) concat(otherTransform: DocumentTransform): DocumentTransform; - // (undocumented) static identity(): DocumentTransform; // @internal @deprecated readonly left?: DocumentTransform; resetCache(): void; // @internal @deprecated readonly right?: DocumentTransform; - // (undocumented) static split(predicate: (document: DocumentNode) => boolean, left: DocumentTransform, right?: DocumentTransform): DocumentTransform & { left: DocumentTransform; right: DocumentTransform; }; - // (undocumented) transformDocument(document: DocumentNode): DocumentNode; } diff --git a/src/utilities/common/stripTypename.ts b/src/utilities/common/stripTypename.ts index ddb125578a9..fa479d2253d 100644 --- a/src/utilities/common/stripTypename.ts +++ b/src/utilities/common/stripTypename.ts @@ -5,6 +5,17 @@ import { omitDeep } from "@apollo/client/utilities/internal"; * * @param value - The object or array that should have `__typename` removed. * @returns The object with all `__typename` properties removed. + * + * @example + * + * ```ts + * stripTypename({ + * __typename: "User", + * id: 1, + * profile: { __typename: "Profile", name: "John Doe" }, + * }); + * // => { id: 1, profile: { name: "John Doe"}} + * ``` */ export function stripTypename(value: T) { return omitDeep(value, "__typename"); diff --git a/src/utilities/graphql/DocumentTransform.ts b/src/utilities/graphql/DocumentTransform.ts index 11573ac341c..0bf13ed2f69 100644 --- a/src/utilities/graphql/DocumentTransform.ts +++ b/src/utilities/graphql/DocumentTransform.ts @@ -14,17 +14,23 @@ type TransformFn = (document: DocumentNode) => DocumentNode; interface DocumentTransformOptions { /** - * Determines whether to cache the transformed GraphQL document. Caching can speed up repeated calls to the document transform for the same input document. Set to `false` to completely disable caching for the document transform. When disabled, this option takes precedence over the [`getCacheKey`](#getcachekey) option. + * Determines whether to cache the transformed GraphQL document. Caching can + * speed up repeated calls to the document transform for the same input + * document. Set to `false` to completely disable caching for the document + * transform. When disabled, this option takes precedence over the [`getCacheKey`](#getcachekey) + * option. * - * The default value is `true`. + * @defaultValue `true` */ cache?: boolean; /** * Defines a custom cache key for a GraphQL document that will determine whether to re-run the document transform when given the same input GraphQL document. Returns an array that defines the cache key. Return `undefined` to disable caching for that GraphQL document. * - * > **Note:** The items in the array may be any type, but also need to be referentially stable to guarantee a stable cache key. + * > [!NOTE] + * > The items in the array can be any type, but each item needs to be + * > referentially stable to guarantee a stable cache key. * - * The default implementation of this function returns the `document` as the cache key. + * @defaultValue `(document) => [document]` */ getCacheKey?: ( document: DocumentNode @@ -35,6 +41,25 @@ function identity(document: DocumentNode) { return document; } +/** + * A class for transforming GraphQL documents. See the [Document transforms + * documentation](https://www.apollographql.com/docs/react/data/document-transforms) for more details on using them. + * + * @example + * + * ```ts + * import { DocumentTransform } from "@apollo/client/utilities"; + * import { visit } from "graphql"; + * + * const documentTransform = new DocumentTransform((doc) => { + * return visit(doc, { + * // ... + * }); + * }); + * + * const transformedDoc = documentTransform.transformDocument(myDocument); + * ``` + */ export class DocumentTransform { private readonly transform: TransformFn; private cached: boolean; @@ -52,6 +77,11 @@ export class DocumentTransform { return [document]; } + /** + * Creates a DocumentTransform that returns the input document unchanged. + * + * @returns The input document + */ static identity() { // No need to cache this transform since it just returns the document // unchanged. This should save a bit of memory that would otherwise be @@ -59,6 +89,26 @@ export class DocumentTransform { return new DocumentTransform(identity, { cache: false }); } + /** + * Creates a DocumentTransform that conditionally applies one of two transforms. + * + * @param predicate - Function that determines which transform to apply + * @param left - Transform to apply when `predicate` returns `true` + * @param right - Transform to apply when `predicate` returns `false`. If not provided, it defaults to `DocumentTransform.identity()`. + * @returns A DocumentTransform that conditionally applies a document transform based on the predicate + * + * @example + * + * ```ts + * import { isQueryOperation } from "@apollo/client/utilities"; + * + * const conditionalTransform = DocumentTransform.split( + * (document) => isQueryOperation(document), + * queryTransform, + * mutationTransform + * ); + * ``` + */ static split( predicate: (document: DocumentNode) => boolean, left: DocumentTransform, @@ -91,7 +141,7 @@ export class DocumentTransform { } /** - * Resets the internal cache of this transform, if it has one. + * Resets the internal cache of this transform, if it is cached. */ resetCache() { if (this.cached) { @@ -121,6 +171,29 @@ export class DocumentTransform { return this.transform(document); } + /** + * Transforms a GraphQL document using the configured transform function. + * + * @remarks + * + * Note that `transformDocument` caches the transformed document. Calling + * `transformDocument` again with the already-transformed document will + * immediately return it. + * + * @param document - The GraphQL document to transform + * @returns The transformed document + * + * @example + * + * ```ts + * const document = gql` + * # ... + * `; + * + * const documentTransform = new DocumentTransform(transformFn); + * const transformedDocument = documentTransform.transformDocument(document); + * ``` + */ transformDocument(document: DocumentNode) { // If a user passes an already transformed result back to this function, // immediately return it. @@ -135,6 +208,22 @@ export class DocumentTransform { return transformedDocument; } + /** + * Combines this document transform with another document transform. The + * returned document transform first applies the current document transform, + * then applies the other document transform. + * + * @param otherTransform - The transform to apply after this one + * @returns A new DocumentTransform that applies both transforms in sequence + * + * @example + * + * ```ts + * const combinedTransform = addTypenameTransform.concat( + * removeDirectivesTransform + * ); + * ``` + */ concat(otherTransform: DocumentTransform): DocumentTransform { return Object.assign( new DocumentTransform( diff --git a/src/utilities/graphql/isFormattedExecutionResult.ts b/src/utilities/graphql/isFormattedExecutionResult.ts index 9781a13e3ea..a743da16c4a 100644 --- a/src/utilities/graphql/isFormattedExecutionResult.ts +++ b/src/utilities/graphql/isFormattedExecutionResult.ts @@ -1,5 +1,32 @@ import type { FormattedExecutionResult } from "graphql"; +/** + * Determines whether the given object is a valid GraphQL execution result + * according to the GraphQL specification. + * + * @remarks + * + * A valid execution result must be an object that contains only `data`, + * `errors`, and/or `extensions` properties. At least one of `data` or `errors` + * must be present. + * + * @param result - The object to test + * @returns `true` if the object conforms to the GraphQL execution result format + * + * @example + * + * ```ts + * import { isFormattedExecutionResult } from "@apollo/client/utilities"; + * + * // Valid execution result + * const validResult = { data: { user: { name: "John" } } }; + * console.log(isFormattedExecutionResult(validResult)); // true + * + * // Invalid - contains non-standard properties + * const invalidResult = { data: {}, customField: "value" }; + * console.log(isFormattedExecutionResult(invalidResult)); // false + * ``` + */ export function isFormattedExecutionResult( result?: object ): result is FormattedExecutionResult { diff --git a/src/utilities/graphql/operations.ts b/src/utilities/graphql/operations.ts index 8ade62343fc..fdb970ecd7d 100644 --- a/src/utilities/graphql/operations.ts +++ b/src/utilities/graphql/operations.ts @@ -11,9 +11,26 @@ function isOperation( /** * Determine if a document is a mutation document. * + * @remarks + * If you are authoring an Apollo link, you might not need this utility. + * Prefer using the `operationType` property the `operation` object instead. + * * @param document - The GraphQL document to check + * @returns A boolean indicating if the document is a mutation operation + * + * @example + * + * ```ts + * import { isMutationOperation } from "@apollo/client/utilities"; * - * @since 3.8.0 + * const mutation = gql` + * mutation MyMutation { + * # ... + * } + * `; + * + * isMutationOperation(mutation); // true + * ``` */ export function isMutationOperation(document: DocumentNode) { return isOperation(document, "mutation"); @@ -22,9 +39,26 @@ export function isMutationOperation(document: DocumentNode) { /** * Determine if a document is a query document. * + * @remarks + * If you are authoring an Apollo link, you might not need this utility. + * Prefer using the `operationType` property the `operation` object instead. + * * @param document - The GraphQL document to check + * @returns A boolean indicating if the document is a query operation + * + * @example * - * @since 3.8.0 + * ```ts + * import { isQueryOperation } from "@apollo/client/utilities"; + * + * const query = gql` + * query MyQuery { + * # ... + * } + * `; + * + * isQueryOperation(query); // true + * ``` */ export function isQueryOperation(document: DocumentNode) { return isOperation(document, "query"); @@ -33,9 +67,26 @@ export function isQueryOperation(document: DocumentNode) { /** * Determine if a document is a subscription document. * + * @remarks + * If you are authoring an Apollo link, you might not need this utility. + * Prefer using the `operationType` property the `operation` object instead. + * * @param document - The GraphQL document to check + * @returns A boolean indicating if the document is a subscription operation + * + * @example + * + * ```ts + * import { isSubscriptionOperation } from "@apollo/client/utilities"; + * + * const subscription = gql` + * subscription MySubscription { + * # ... + * } + * `; * - * @since 3.8.0 + * isSubscriptionOperation(subscription); // true + * ``` */ export function isSubscriptionOperation(document: DocumentNode) { return isOperation(document, "subscription"); diff --git a/src/utilities/graphql/storeUtils.ts b/src/utilities/graphql/storeUtils.ts index a512a524883..099630ff715 100644 --- a/src/utilities/graphql/storeUtils.ts +++ b/src/utilities/graphql/storeUtils.ts @@ -9,6 +9,15 @@ export interface Reference { * Determines if a given object is a reference object. * * @param obj - The object to check if its a reference object + * + * @example + * + * ```ts + * import { isReference } from "@apollo/client/utilities"; + * + * isReference({ __ref: "User:1" }); // true + * isReference({ __typename: "User", id: 1 }); // false + * ``` */ export function isReference(obj: any): obj is Reference { return Boolean( diff --git a/src/utilities/graphql/transform.ts b/src/utilities/graphql/transform.ts index 23358e003c3..6d2cb117e81 100644 --- a/src/utilities/graphql/transform.ts +++ b/src/utilities/graphql/transform.ts @@ -10,9 +10,20 @@ const TYPENAME_FIELD: FieldNode = { }; /** - * Adds `__typename` to all selection sets in the document. + * Adds `__typename` to all selection sets in the document except for the root + * selection set. * * @param doc - The `ASTNode` to add `__typename` to + * + * @example + * + * ```ts + * const document = gql` + * # ... + * `; + * + * const withTypename = addTypenameToDocument(document); + * ``` */ export const addTypenameToDocument = Object.assign( function (doc: TNode): TNode { diff --git a/src/utilities/internal/canonicalStringify.ts b/src/utilities/internal/canonicalStringify.ts index 34916766a7f..70e9e557c0f 100644 --- a/src/utilities/internal/canonicalStringify.ts +++ b/src/utilities/internal/canonicalStringify.ts @@ -6,21 +6,44 @@ import { AutoCleanedStrongCache } from "./caches.js"; import { registerGlobalCache } from "./getMemoryInternals.js"; /** - * Like JSON.stringify, but with object keys always sorted in the same order. + * Serializes a value to JSON with object keys in a consistent, sorted order. * - * To achieve performant sorting, this function uses a Map from JSON-serialized + * @remarks + * + * Unlike `JSON.stringify()`, this function ensures that object keys are always + * serialized in the same alphabetical order, regardless of their original order. + * This makes it suitable for creating consistent cache keys from objects, + * comparing objects by their serialized representation, or generating + * deterministic hashes of objects. + * + * To achieve performant sorting, this function uses a `Map` from JSON-serialized * arrays of keys (in any order) to sorted arrays of the same keys, with a * single sorted array reference shared by all permutations of the keys. * - * As a drawback, this function will add a little bit more memory for every - * object encountered that has different (more, less, a different order of) keys - * than in the past. + * As a drawback, this function will add a little more memory for every object + * encountered that has different (more, less, a different order of) keys than + * in the past. * * In a typical application, this extra memory usage should not play a * significant role, as `canonicalStringify` will be called for only a limited * number of object shapes, and the cache will not grow beyond a certain point. - * But in some edge cases, this could be a problem, so we provide - * canonicalStringify.reset() as a way of clearing the cache. + * But in some edge cases, this could be a problem. Use canonicalStringify.reset() + * as a way to clear the memoization cache. + * + * @param value - The value to stringify + * @returns JSON string with consistently ordered object keys + * + * @example + * + * ```ts + * import { canonicalStringify } from "@apollo/client/utilities"; + * + * const obj1 = { b: 2, a: 1 }; + * const obj2 = { a: 1, b: 2 }; + * + * console.log(canonicalStringify(obj1)); // '{"a":1,"b":2}' + * console.log(canonicalStringify(obj2)); // '{"a":1,"b":2}' + * ``` */ export const canonicalStringify = Object.assign( function canonicalStringify(value: any): string { diff --git a/src/utilities/internal/getMainDefinition.ts b/src/utilities/internal/getMainDefinition.ts index 60615004f37..98c27fb0204 100644 --- a/src/utilities/internal/getMainDefinition.ts +++ b/src/utilities/internal/getMainDefinition.ts @@ -9,11 +9,41 @@ import { newInvariantError } from "@apollo/client/utilities/invariant"; import { checkDocument } from "./checkDocument.js"; /** - * Returns the first operation definition found in this document. - * If no operation definition is found, the first fragment definition will be returned. - * If no definitions are found, an error will be thrown. + * Returns the first operation definition from a GraphQL document. The function + * prioritizes operation definitions over fragment definitions, which makes it + * suitable for documents that may contain both. If no operation definition is + * found, the first fragment definition will be returned. If no definitions are + * found, an error is thrown. * - * @internal + * @remarks + * + * Use this function when you need to perform more advanced tasks with the main + * definition AST node. If you want to determine when a document is a specific + * operation type, prefer the `isQueryOperation`, `isMutationOperation`, and + * `isSubscriptionOperation` utility functions instead. + * + * @param queryDoc - The GraphQL document to extract the definition from + * @returns The main operation or fragment definition AST node + * + * @example + * + * ```ts + * import { gql } from "@apollo/client"; + * import { getMainDefinition } from "@apollo/client/utilities"; + * + * const query = gql` + * query GetUser($id: ID!) { + * user(id: $id) { + * name + * email + * } + * } + * `; + * + * const definition = getMainDefinition(query); + * ``` + * + * @throws When the document contains no operation or fragment definitions */ export function getMainDefinition( queryDoc: DocumentNode