UNPKG

@sanity/client

Version:

Client for retrieving, creating and patching data from Sanity.io

384 lines (351 loc) • 13.1 kB
import type {Observable} from 'rxjs' import type {ObservableSanityClient, SanityClient} from '../SanityClient' import type { Any, BaseMutationOptions, IdentifiedSanityDocumentStub, MultipleMutationResult, Mutation, MutationSelection, PatchOperations, SanityDocument, SanityDocumentStub, SingleMutationResult, TransactionAllDocumentIdsMutationOptions, TransactionAllDocumentsMutationOptions, TransactionFirstDocumentIdMutationOptions, TransactionFirstDocumentMutationOptions, } from '../types' import * as validators from '../validators' import {ObservablePatch, Patch} from './patch' /** @public */ export type PatchBuilder = (patch: Patch) => Patch /** @public */ export type ObservablePatchBuilder = (patch: ObservablePatch) => ObservablePatch const defaultMutateOptions = {returnDocuments: false} /** @internal */ export class BaseTransaction { protected operations: Mutation[] protected trxId?: string constructor(operations: Mutation[] = [], transactionId?: string) { this.operations = operations this.trxId = transactionId } /** * Creates a new Sanity document. If `_id` is provided and already exists, the mutation will fail. If no `_id` is given, one will automatically be generated by the database. * The operation is added to the current transaction, ready to be commited by `commit()` * * @param doc - Document to create. Requires a `_type` property. */ create<R extends Record<string, Any> = Record<string, Any>>(doc: SanityDocumentStub<R>): this { validators.validateObject('create', doc) return this._add({create: doc}) } /** * Creates a new Sanity document. If a document with the same `_id` already exists, the create operation will be ignored. * The operation is added to the current transaction, ready to be commited by `commit()` * * @param doc - Document to create if it does not already exist. Requires `_id` and `_type` properties. */ createIfNotExists<R extends Record<string, Any> = Record<string, Any>>( doc: IdentifiedSanityDocumentStub<R>, ): this { const op = 'createIfNotExists' validators.validateObject(op, doc) validators.requireDocumentId(op, doc) return this._add({[op]: doc}) } /** * Creates a new Sanity document, or replaces an existing one if the same `_id` is already used. * The operation is added to the current transaction, ready to be commited by `commit()` * * @param doc - Document to create or replace. Requires `_id` and `_type` properties. */ createOrReplace<R extends Record<string, Any> = Record<string, Any>>( doc: IdentifiedSanityDocumentStub<R>, ): this { const op = 'createOrReplace' validators.validateObject(op, doc) validators.requireDocumentId(op, doc) return this._add({[op]: doc}) } /** * Deletes the document with the given document ID * The operation is added to the current transaction, ready to be commited by `commit()` * * @param documentId - Document ID to delete */ delete(documentId: string): this { validators.validateDocumentId('delete', documentId) return this._add({delete: {id: documentId}}) } /** * Gets the current transaction ID, if any */ transactionId(): string | undefined /** * Set the ID of this transaction. * * @param id - Transaction ID */ transactionId(id: string): this transactionId(id?: string): this | string | undefined { if (!id) { return this.trxId } this.trxId = id return this } /** * Return a plain JSON representation of the transaction */ serialize(): Mutation[] { return [...this.operations] } /** * Return a plain JSON representation of the transaction */ toJSON(): Mutation[] { return this.serialize() } /** * Clears the transaction of all operations */ reset(): this { this.operations = [] return this } protected _add(mut: Mutation): this { this.operations.push(mut) return this } } /** @public */ export class Transaction extends BaseTransaction { #client?: SanityClient constructor(operations?: Mutation[], client?: SanityClient, transactionId?: string) { super(operations, transactionId) this.#client = client } /** * Clones the transaction */ clone(): Transaction { return new Transaction([...this.operations], this.#client, this.trxId) } /** * Commit the transaction, returning a promise that resolves to the first mutated document * * @param options - Options for the mutation operation */ commit<R extends Record<string, Any>>( options: TransactionFirstDocumentMutationOptions, ): Promise<SanityDocument<R>> /** * Commit the transaction, returning a promise that resolves to an array of the mutated documents * * @param options - Options for the mutation operation */ commit<R extends Record<string, Any>>( options: TransactionAllDocumentsMutationOptions, ): Promise<SanityDocument<R>[]> /** * Commit the transaction, returning a promise that resolves to a mutation result object * * @param options - Options for the mutation operation */ commit(options: TransactionFirstDocumentIdMutationOptions): Promise<SingleMutationResult> /** * Commit the transaction, returning a promise that resolves to a mutation result object * * @param options - Options for the mutation operation */ commit(options: TransactionAllDocumentIdsMutationOptions): Promise<MultipleMutationResult> /** * Commit the transaction, returning a promise that resolves to a mutation result object * * @param options - Options for the mutation operation */ commit(options?: BaseMutationOptions): Promise<MultipleMutationResult> commit<R extends Record<string, Any> = Record<string, Any>>( options?: | TransactionFirstDocumentMutationOptions | TransactionAllDocumentsMutationOptions | TransactionFirstDocumentIdMutationOptions | TransactionAllDocumentIdsMutationOptions | BaseMutationOptions, ): Promise< SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult > { if (!this.#client) { throw new Error( 'No `client` passed to transaction, either provide one or pass the ' + 'transaction to a clients `mutate()` method', ) } return this.#client.mutate<R>( this.serialize() as Any, Object.assign({transactionId: this.trxId}, defaultMutateOptions, options || {}), ) } /** * Performs a patch on the given document ID. Can either be a builder function or an object of patch operations. * The operation is added to the current transaction, ready to be commited by `commit()` * * @param documentId - Document ID to perform the patch operation on * @param patchOps - Operations to perform, or a builder function */ patch(documentId: string, patchOps?: PatchBuilder | PatchOperations): this /** * Performs a patch on the given selection. Can either be a builder function or an object of patch operations. * * @param selection - An object with `query` and optional `params`, defining which document(s) to patch * @param patchOps - Operations to perform, or a builder function */ patch(patch: MutationSelection, patchOps?: PatchBuilder | PatchOperations): this /** * Adds the given patch instance to the transaction. * The operation is added to the current transaction, ready to be commited by `commit()` * * @param patch - Patch to execute */ patch(patch: Patch): this patch( patchOrDocumentId: Patch | MutationSelection | string, patchOps?: PatchBuilder | PatchOperations, ): this { const isBuilder = typeof patchOps === 'function' const isPatch = typeof patchOrDocumentId !== 'string' && patchOrDocumentId instanceof Patch const isMutationSelection = typeof patchOrDocumentId === 'object' && ('query' in patchOrDocumentId || 'id' in patchOrDocumentId) // transaction.patch(client.patch('documentId').inc({visits: 1})) if (isPatch) { return this._add({patch: patchOrDocumentId.serialize()}) } // patch => patch.inc({visits: 1}).set({foo: 'bar'}) if (isBuilder) { const patch = patchOps(new Patch(patchOrDocumentId, {}, this.#client)) if (!(patch instanceof Patch)) { throw new Error('function passed to `patch()` must return the patch') } return this._add({patch: patch.serialize()}) } /* * transaction.patch( * {query: "*[_type == 'person' && points >= $threshold]", params: { threshold: 100 }}, * {dec: { points: 100 }, inc: { bonuses: 1 }} * ) */ if (isMutationSelection) { const patch = new Patch(patchOrDocumentId, patchOps || {}, this.#client) return this._add({patch: patch.serialize()}) } return this._add({patch: {id: patchOrDocumentId, ...patchOps}}) } } /** @public */ export class ObservableTransaction extends BaseTransaction { #client?: ObservableSanityClient constructor(operations?: Mutation[], client?: ObservableSanityClient, transactionId?: string) { super(operations, transactionId) this.#client = client } /** * Clones the transaction */ clone(): ObservableTransaction { return new ObservableTransaction([...this.operations], this.#client, this.trxId) } /** * Commit the transaction, returning an observable that produces the first mutated document * * @param options - Options for the mutation operation */ commit<R extends Record<string, Any>>( options: TransactionFirstDocumentMutationOptions, ): Observable<SanityDocument<R>> /** * Commit the transaction, returning an observable that produces an array of the mutated documents * * @param options - Options for the mutation operation */ commit<R extends Record<string, Any>>( options: TransactionAllDocumentsMutationOptions, ): Observable<SanityDocument<R>[]> /** * Commit the transaction, returning an observable that produces a mutation result object * * @param options - Options for the mutation operation */ commit(options: TransactionFirstDocumentIdMutationOptions): Observable<SingleMutationResult> /** * Commit the transaction, returning an observable that produces a mutation result object * * @param options - Options for the mutation operation */ commit(options: TransactionAllDocumentIdsMutationOptions): Observable<MultipleMutationResult> /** * Commit the transaction, returning an observable that produces a mutation result object * * @param options - Options for the mutation operation */ commit(options?: BaseMutationOptions): Observable<MultipleMutationResult> commit<R extends Record<string, Any> = Record<string, Any>>( options?: | TransactionFirstDocumentMutationOptions | TransactionAllDocumentsMutationOptions | TransactionFirstDocumentIdMutationOptions | TransactionAllDocumentIdsMutationOptions | BaseMutationOptions, ): Observable< SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult > { if (!this.#client) { throw new Error( 'No `client` passed to transaction, either provide one or pass the ' + 'transaction to a clients `mutate()` method', ) } return this.#client.mutate<R>( this.serialize() as Any, Object.assign({transactionId: this.trxId}, defaultMutateOptions, options || {}), ) } /** * Performs a patch on the given document ID. Can either be a builder function or an object of patch operations. * The operation is added to the current transaction, ready to be commited by `commit()` * * @param documentId - Document ID to perform the patch operation on * @param patchOps - Operations to perform, or a builder function */ patch(documentId: string, patchOps?: ObservablePatchBuilder | PatchOperations): this /** * Adds the given patch instance to the transaction. * The operation is added to the current transaction, ready to be commited by `commit()` * * @param patch - ObservablePatch to execute */ patch(patch: ObservablePatch): this patch( patchOrDocumentId: ObservablePatch | string, patchOps?: ObservablePatchBuilder | PatchOperations, ): this { const isBuilder = typeof patchOps === 'function' const isPatch = typeof patchOrDocumentId !== 'string' && patchOrDocumentId instanceof ObservablePatch // transaction.patch(client.patch('documentId').inc({visits: 1})) if (isPatch) { return this._add({patch: patchOrDocumentId.serialize()}) } // patch => patch.inc({visits: 1}).set({foo: 'bar'}) if (isBuilder) { const patch = patchOps(new ObservablePatch(patchOrDocumentId, {}, this.#client)) if (!(patch instanceof ObservablePatch)) { throw new Error('function passed to `patch()` must return the patch') } return this._add({patch: patch.serialize()}) } return this._add({patch: {id: patchOrDocumentId, ...patchOps}}) } }