JSPM

@sanity/client

5.0.0-esm.8
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 776082
  • Score
    100M100P100Q180778F
  • License MIT

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

Package Exports

  • @sanity/client
  • @sanity/client/package.json

Readme

@sanity/client

npm stat npm version gzip size size

Javascript client for Sanity. Works in Node.js, Bun, Deno, Edge Runtime and modern browsers.

Table of contents

Requirements

Sanity Client transpiles syntax for modern browsers. The JavaScript runtime must support ES6 features such as class, rest parameters, spread syntax and more. For ES5 environments you'll need to transpile @sanity/client and its dependencies with your own bundler, and have a global ES6-compliant Promise available. If your runtime environment doesn't provide a spec compliant Promise implementation, we recommend using native-promise-only, es6-promise or another spec-compliant implementation. See this article for more information.

Installation

The client can be installed from npm:

npm install @sanity/client

API

Creating a client instance

const client = createClient(options)

Initializes a new Sanity Client. Required options are projectId, dataset, and apiVersion. Setting a value for useCdn is encouraged.

ESM

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'bikeshop',
  apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
  token: 'sanity-auth-token', // or leave blank for unauthenticated usage
  useCdn: true, // `false` if you want to ensure fresh data
})

export default client

CommonJS

const {createClient} = require('@sanity/client')

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'bikeshop',
  apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
  token: 'sanity-auth-token', // or leave blank for unauthenticated usage
  useCdn: true, // `false` if you want to ensure fresh data
})

module.exports = client

TypeScript

import {createClient, type ClientConfig} from '@sanity/client'

const client: ClientConfig = {
  projectId: 'your-project-id',
  dataset: 'bikeshop',
  apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
  token: 'sanity-auth-token', // or leave blank for unauthenticated usage
  useCdn: true, // `false` if you want to ensure fresh data
}

export default createClient(config)

Bun

bun init
bun add @sanity/client
open index.ts
// index.ts
import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'bikeshop',
  apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
  token: 'sanity-auth-token', // or leave blank for unauthenticated usage
  useCdn: true, // `false` if you want to ensure fresh data
})

const data = await client.fetch(`count(*[])`)

console.write(`Number of documents: ${data}`)
bun run index.ts
# Number of documents ${number}

Deno

deno init
open main.ts
// main.ts
import {createClient} from 'https://esm.sh/@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'bikeshop',
  apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
  token: 'sanity-auth-token', // or leave blank for unauthenticated usage
  useCdn: true, // `false` if you want to ensure fresh data
})

const data = await client.fetch(`count(*[])`)

console.log(`Number of documents: ${data}`)
deno run --allow-net --allow-env main.ts
# Number of documents ${number}

Edge Runtime

npm install next
// pages/api/total.ts
import {createClient} from '@sanity/client'
import type {NextRequest} from 'next/server'

export const config = {
  runtime: 'edge',
}

export default async function handler(req: NextRequest) {
  const client = createClient({
    projectId: 'your-project-id',
    dataset: 'bikeshop',
    apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
    token: 'sanity-auth-token', // or leave blank for unauthenticated usage
    useCdn: true, // `false` if you want to ensure fresh data
  })

  return new Response(
    JSON.stringify({
      count: await client.fetch(`count(*[])`),
    }),
    {
      status: 200,
      headers: {
        'content-type': 'application/json',
      },
    }
  )
}
npx next dev
# Open http://localhost:3000/api/total
# {"count": number}

Browser ESM CDN

Using esm.sh you can either load the client using a <script type="module"> tag:

<script type="module">
  import {createClient} from 'https://esm.sh/@sanity/client'

  const client = createClient({
    projectId: 'your-project-id',
    dataset: 'bikeshop',
    apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
    token: 'sanity-auth-token', // or leave blank for unauthenticated usage
    useCdn: true, // `false` if you want to ensure fresh data
  })

  const data = await client.fetch(`count(*[])`)
  document.getElementById('results').innerText = `Number of documents: ${data}`
</script>
<div id="results"></div>

Or from anywhere using a dynamic import():

// You can run this snippet from your browwser DevTools console.
// Super handy when you're quickly testing out queries.
const {createClient} = await import('https://esm.sh/@sanity/client')
const client = createClient({
  projectId: 'your-project-id',
  dataset: 'bikeshop',
  apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
  token: 'sanity-auth-token', // or leave blank for unauthenticated usage
  useCdn: true, // `false` if you want to ensure fresh data
})

const data = await client.fetch(`count(*[])`)
console.log(`Number of documents: ${data}`)

UMD

Loading the UMD script creates a SanityClient global that have the same exports as import * as SanityClient from '@sanity/client':

<script src="https://unpkg.com/@sanity/client"></script>
<!-- Unminified build for debugging -->
<!--<script src="https://unpkg.com/@sanity/client/umd/sanityClient.js"></script>-->
<script>
  const {createClient} = SanityClient

  const client = createClient({
    projectId: 'your-project-id',
    dataset: 'bikeshop',
    apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
    token: 'sanity-auth-token', // or leave blank for unauthenticated usage
    useCdn: true, // `false` if you want to ensure fresh data
  })

  client.fetch(`count(*[])`).then((data) => console.log(`Number of documents: ${data}`))
</script>

The require-unpkg library lets you consume npm packages from unpkg.com similar to how esm.sh lets you import() anything:

<div id="results"></div>
<script src="https://unpkg.com/require-unpkg"></script>
<script>
  ;(async () => {
    // const {createClient} = await require('@sanity/client')
    const [$, {createClient}] = await require(['jquery', '@sanity/client'])

    const client = createClient({
      projectId: 'your-project-id',
      dataset: 'bikeshop',
      apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
      token: 'sanity-auth-token', // or leave blank for unauthenticated usage
      useCdn: true, // `false` if you want to ensure fresh data
    })

    const data = await client.fetch(`count(*[])`)
    $('#results').text(`Number of documents: ${data}`)
  })()
</script>

Specifying API version

Sanity uses ISO dates (YYYY-MM-DD) in UTC timezone for versioning. The explanation for this can be found in the documentation

In general, unless you know what API version you want to use, you'll want to statically set it to today's UTC date when starting a new project. By doing this, you'll get all the latest bugfixes and features, while locking the API to prevent breaking changes.

Note: Do not be tempted to use a dynamic value for the apiVersion. The reason for setting a static value is to prevent unexpected, breaking changes.

In future versions, specifying an API version will be required. For now (to maintain backwards compatiblity) not specifying a version will trigger a deprecation warning and fall back to using v1.

Performing queries

const query = '*[_type == "bike" && seats >= $minSeats] {name, seats}'
const params = {minSeats: 2}

client.fetch(query, params).then((bikes) => {
  console.log('Bikes with more than one seat:')
  bikes.forEach((bike) => {
    console.log(`${bike.name} (${bike.seats} seats)`)
  })
})

client.fetch(query, params = {})

Perform a query using the given parameters (if any).

Listening to queries

const query = '*[_type == "comment" && authorId != $ownerId]'
const params = {ownerId: 'bikeOwnerUserId'}

const subscription = client.listen(query, params).subscribe((update) => {
  const comment = update.result
  console.log(`${comment.author} commented: ${comment.text}`)
})

// to unsubscribe later on
subscription.unsubscribe()

client.listen(query, params = {}, options = {includeResult: true})

Open a query that listens for updates on matched documents, using the given parameters (if any). The return value is an RxJS Observable. When calling .subscribe() on the returned observable, a subscription is returned, and this can be used to unsubscribe from the query later on by calling subscription.unsubscribe()

The update events which are emitted always contain mutation, which is an object containing the mutation which triggered the document to appear as part of the query.

By default, the emitted update event will also contain a result property, which contains the document with the mutation applied to it. In case of a delete mutation, this property will not be present, however. You can also tell the client not to return the document (to save bandwidth, or in cases where the mutation or the document ID is the only relevant factor) by setting the includeResult property to false in the options.

Likewise, you can also have the client return the document before the mutation was applied, by setting includePreviousRevision to true in the options, which will include a previous property in each emitted object.

Fetch a single document

This will fetch a document from the Doc endpoint. This endpoint cuts through any caching/indexing middleware that may involve delayed processing. As it is less scalable/performant than the other query mechanisms, it should be used sparingly. Performing a query is usually a better option.

client.getDocument('bike-123').then((bike) => {
  console.log(`${bike.name} (${bike.seats} seats)`)
})

Fetch multiple documents in one go

This will fetch multiple documents in one request from the Doc endpoint. This endpoint cuts through any caching/indexing middleware that may involve delayed processing. As it is less scalable/performant than the other query mechanisms, it should be used sparingly. Performing a query is usually a better option.

client.getDocuments(['bike123', 'bike345']).then(([bike123, bike345]) => {
  console.log(`Bike 123: ${bike123.name} (${bike123.seats} seats)`)
  console.log(`Bike 345: ${bike345.name} (${bike345.seats} seats)`)
})

Note: Unlike in the HTTP API, the order/position of documents is preserved based on the original array of IDs. If any of the documents are missing, they will be replaced by a null entry in the returned array:

const ids = ['bike123', 'nonexistent-document', 'bike345']
client.getDocuments(ids).then((docs) => {
  // the docs array will be:
  // [{_id: 'bike123', ...}, null, {_id: 'bike345', ...}]
})

Creating documents

const doc = {
  _type: 'bike',
  name: 'Sanity Tandem Extraordinaire',
  seats: 2,
}

client.create(doc).then((res) => {
  console.log(`Bike was created, document ID is ${res._id}`)
})

client.create(doc)
client.create(doc, mutationOptions)

Create a document. Argument is a plain JS object representing the document. It must contain a _type attribute. It may contain an _id. If an ID is not specified, it will automatically be created.

To create a draft document, prefix the document ID with drafts. - eg _id: 'drafts.myDocumentId'. To auto-generate a draft document ID, set _id to drafts. (nothing after the .).

Creating/replacing documents

const doc = {
  _id: 'my-bike',
  _type: 'bike',
  name: 'Sanity Tandem Extraordinaire',
  seats: 2,
}

client.createOrReplace(doc).then((res) => {
  console.log(`Bike was created, document ID is ${res._id}`)
})

client.createOrReplace(doc)
client.createOrReplace(doc, mutationOptions)

If you are not sure whether or not a document exists but want to overwrite it if it does, you can use the createOrReplace() method. When using this method, the document must contain an _id attribute.

Creating if not already present

const doc = {
  _id: 'my-bike',
  _type: 'bike',
  name: 'Sanity Tandem Extraordinaire',
  seats: 2,
}

client.createIfNotExists(doc).then((res) => {
  console.log('Bike was created (or was already present)')
})

client.createIfNotExists(doc)
client.createIfNotExists(doc, mutationOptions)

If you want to create a document if it does not already exist, but fall back without error if it does, you can use the createIfNotExists() method. When using this method, the document must contain an _id attribute.

Patch/update a document

client
  .patch('bike-123') // Document ID to patch
  .set({inStock: false}) // Shallow merge
  .inc({numSold: 1}) // Increment field by count
  .commit() // Perform the patch and return a promise
  .then((updatedBike) => {
    console.log('Hurray, the bike is updated! New document:')
    console.log(updatedBike)
  })
  .catch((err) => {
    console.error('Oh no, the update failed: ', err.message)
  })

Modify a document. patch takes a document ID. set merges the partialDoc with the stored document. inc increments the given field with the given numeric value. commit executes the given patch. Returns the updated object.

client.patch()
  [operations]
  .commit(mutationOptions)

Setting a field only if not already present

client.patch('bike-123').setIfMissing({title: 'Untitled bike'}).commit()

Removing/unsetting fields

client.patch('bike-123').unset(['title', 'price']).commit()

Incrementing/decrementing numbers

client
  .patch('bike-123')
  .inc({price: 88, numSales: 1}) // Increment `price` by 88, `numSales` by 1
  .dec({inStock: 1}) // Decrement `inStock` by 1
  .commit()

Patch a document only if revision matches

You can use the ifRevisionId(rev) method to specify that you only want the patch to be applied if the stored document matches a given revision.

client
  .patch('bike-123')
  .ifRevisionId('previously-known-revision')
  .set({title: 'Little Red Tricycle'})
  .commit()

Adding elements to an array

The patch operation insert takes a location (before, after or replace), a path selector and an array of elements to insert.

client
  .patch('bike-123')
  // Ensure that the `reviews` arrays exists before attempting to add items to it
  .setIfMissing({reviews: []})
  // Add the items after the last item in the array (append)
  .insert('after', 'reviews[-1]', [{title: 'Great bike!', stars: 5}])
  .commit({
    // Adds a `_key` attribute to array items, unique within the array, to
    // ensure it can be addressed uniquely in a real-time collaboration context
    autoGenerateArrayKeys: true,
  })

Appending/prepending elements to an array

The operations of appending and prepending to an array are so common that they have been given their own methods for better readability:

client
  .patch('bike-123')
  .setIfMissing({reviews: []})
  .append('reviews', [{title: 'Great bike!', stars: 5}])
  .commit({autoGenerateArrayKeys: true})

Deleting an element from an array

Each entry in the unset array can be either an attribute or a JSON path.

In this example, we remove the first review and the review with _key: 'abc123' from the bike.reviews array:

const reviewsToRemove = ['reviews[0]', 'reviews[_key=="abc123"]']
client.patch('bike-123').unset(reviewsToRemove).commit()

Delete documents

A single document can be deleted by specifying a document ID:

client.delete(docId)
client.delete(docId, mutationOptions)

client
  .delete('bike-123')
  .then(() => {
    console.log('Bike deleted')
  })
  .catch((err) => {
    console.error('Delete failed: ', err.message)
  })

One or more documents can be deleted by specifying a GROQ query (and optionally, params):

client.delete({ query: "GROQ query", params: { key: value } })

// Without params
client
  .delete({query: '*[_type == "bike"][0]'})
  .then(() => {
    console.log('The document matching *[_type == "bike"][0] was deleted')
  })
  .catch((err) => {
    console.error('Delete failed: ', err.message)
  })
// With params
client
  .delete({query: '*[_type == $type][0..1]', params: {type: 'bike'}})
  .then(() => {
    console.log('The documents matching *[_type == "bike"][0..1] was deleted')
  })
  .catch((err) => {
    console.error('Delete failed: ', err.message)
  })

Multiple mutations in a transaction

const namePatch = client.patch('bike-310').set({name: 'A Bike To Go'})

client
  .transaction()
  .create({name: 'Sanity Tandem Extraordinaire', seats: 2})
  .delete('bike-123')
  .patch(namePatch)
  .commit()
  .then((res) => {
    console.log('Whole lot of stuff just happened')
  })
  .catch((err) => {
    console.error('Transaction failed: ', err.message)
  })

client.transaction().create(doc).delete(docId).patch(patch).commit()

Create a transaction to perform chained mutations.

client
  .transaction()
  .create({name: 'Sanity Tandem Extraordinaire', seats: 2})
  .patch('bike-123', (p) => p.set({inStock: false}))
  .commit()
  .then((res) => {
    console.log('Bike created and a different bike is updated')
  })
  .catch((err) => {
    console.error('Transaction failed: ', err.message)
  })

client.transaction().create(doc).patch(docId, p => p.set(partialDoc)).commit()

A patch can be performed inline on a transaction.

Clientless patches & transactions

Transactions and patches can also be built outside the scope of a client:

import {createClient, Patch, Transaction} from '@sanity/client'
const client = createClient({
  projectId: 'your-project-id',
  dataset: 'bikeshop',
})

// Patches:
const patch = new Patch('<documentId>')
client.mutate(patch.inc({count: 1}).unset(['visits']))

// Transactions:
const transaction = new Transaction().create({_id: '123', name: 'FooBike'}).delete('someDocId')

client.mutate(transaction)

const patch = new Patch(docId)

const transaction = new Transaction()

An important note on this approach is that you cannot call commit() on transactions or patches instantiated this way, instead you have to pass them to client.mutate()

Uploading assets

Assets can be uploaded using the client.assets.upload(...) method.

client.assets.upload(type: 'file' | image', body: File | Blob | Buffer | NodeStream, options = {}): Promise<AssetDocument>

👉 Read more about assets in Sanity

Examples: Uploading assets from Node.js

// Upload a file from the file system
client.assets
  .upload('file', fs.createReadStream('myFile.txt'), {filename: 'myFile.txt'})
  .then((document) => {
    console.log('The file was uploaded!', document)
  })
  .catch((error) => {
    console.error('Upload failed:', error.message)
  })
// Upload an image file from the file system
client.assets
  .upload('image', fs.createReadStream('myImage.jpg'), {filename: 'myImage.jpg'})
  .then((document) => {
    console.log('The image was uploaded!', document)
  })
  .catch((error) => {
    console.error('Upload failed:', error.message)
  })

Examples: Uploading assets from the Browser

// Create a file with "foo" as its content
const file = new File(['foo'], 'foo.txt', {type: 'text/plain'})
// Upload it
client.assets
  .upload('file', file)
  .then((document) => {
    console.log('The file was uploaded!', document)
  })
  .catch((error) => {
    console.error('Upload failed:', error.message)
  })
// Draw something on a canvas and upload as image
const canvas = document.getElementById('someCanvas')
const ctx = canvas.getContext('2d')
ctx.fillStyle = '#f85040'
ctx.fillRect(0, 0, 50, 50)
ctx.fillStyle = '#fff'
ctx.font = '10px monospace'
ctx.fillText('Sanity', 8, 30)
canvas.toBlob(uploadImageBlob, 'image/png')

function uploadImageBlob(blob) {
  client.assets
    .upload('image', blob, {contentType: 'image/png', filename: 'someText.png'})
    .then((document) => {
      console.log('The image was uploaded!', document)
    })
    .catch((error) => {
      console.error('Upload failed:', error.message)
    })
}

Examples: Specify image metadata to extract

// Extract palette of colors as well as GPS location from exif
client.assets
  .upload('image', someFile, {extract: ['palette', 'location']})
  .then((document) => {
    console.log('The file was uploaded!', document)
  })
  .catch((error) => {
    console.error('Upload failed:', error.message)
  })

Deleting an asset

Deleting an asset document will also trigger deletion of the actual asset.

client.delete(assetDocumentId: string): Promise
client.delete('image-abc123_someAssetId-500x500-png').then((result) => {
  console.log('deleted imageAsset', result)
})

Mutation options

The following options are available for mutations, and can be applied either as the second argument to create(), createOrReplace, createIfNotExist, delete() and mutate() - or as an argument to the commit() method on patches and transactions.

  • visibility ('sync'|'async'|'deferred') - default 'sync'
    • sync: request will not return until the requested changes are visible to subsequent queries.
    • async: request will return immediately when the changes have been committed, but it might still be a second or more until changes are reflected in a query. Unless you are immediately re-querying for something that includes the mutated data, this is the preferred choice.
    • deferred: fastest way to write - bypasses real-time indexing completely, and should be used in cases where you are bulk importing/mutating a large number of documents and don't need to see that data in a query for tens of seconds.
  • dryRun (true|false) - default false. If true, the mutation will be a dry run - the response will be identical to the one returned had this property been omitted or false (including error responses) but no documents will be affected.
  • autoGenerateArrayKeys (true|false) - default false. If true, the mutation API will automatically add _key attributes to objects in arrays that is missing them. This makes array operations more robust by having a unique key within the array available for selections, which helps prevent race conditions in real-time, collaborative editing.

Get client configuration

const config = client.config()
console.log(config.dataset)

client.config()

Get client configuration.

Set client configuration

client.config({dataset: 'newDataset'})

client.config(options)

Set client configuration. Required options are projectId and dataset.

Release new version

Run "CI & Release" workflow. Make sure to select the main branch and check "Release new version".

Semantic release will only release on configured branches, so it is safe to run release on any branch.

License

MIT © Sanity.io

Migrate

From v4

No longer shipping ES5

The target is changed to modern browsers that supports ES6 class, {...rest} syntax and more. You may need to update your bundler to a recent major version. Or you could configure your bundler to transpile @sanity/client, and get-it, which is the engine that powers @sanity/client and uses the same output target.

Node.js v12 no longer supported

Upgrade to the LTS release, or one of the Maintenance releases.

The default export is replaced with the named export createClient

Before:

import createClient from '@sanity/client'
const client = createClient()
import SanityClient from '@sanity/client'
const client = new SanityClient()

After:

import {createClient} from '@sanity/client'
const client = createClient()

client.assets.delete is removed

Before:

client.assets.delete('image', 'abc123_foobar-123x123-png')
client.assets.delete('image', 'image-abc123_foobar-123x123-png')
client.assets.delete({_id: 'image-abc123_foobar-123x123-png'})

After:

client.delete('image-abc123_foobar-123x123-png')

client.assets.getImageUrl is removed, replace with @sanity/image-url

Before:

import createClient from '@sanity/client'
const client = createClient({projectId: 'abc123', dataset: 'foo'})

client.assets.getImageUrl('image-abc123_foobar-123x123-png')
client.assets.getImageUrl('image-abc123_foobar-123x123-png', {auto: 'format'})
client.assets.getImageUrl({_ref: 'image-abc123_foobar-123x123-png'})
client.assets.getImageUrl({_ref: 'image-abc123_foobar-123x123-png'}, {auto: 'format'})

After:

npm install @sanity/image-url
import imageUrlBuilder from '@sanity/image-url'
const builder = imageUrlBuilder({projectId: 'abc123', dataset: 'foo'})
const urlFor = (source) => builder.image(source)

urlFor('image-abc123_foobar-123x123-png').url()
urlFor('image-abc123_foobar-123x123-png').auto('format').url()
urlFor({_ref: 'image-abc123_foobar-123x123-png'}).url()
urlFor({_ref: 'image-abc123_foobar-123x123-png'}).auto('format').url()

SanityClient static properties moved to named exports

Before:

import SanityClient from '@sanity/client'

const {Patch, Transaction, ClientError, ServerError, requester} = SanityClient

After:

import {Patch, Transaction, ClientError, ServerError, requester} from '@sanity/client'

client.clientConfig is removed, replace with client.config()

Before:

import createClient from '@sanity/client'
const client = createClient()

console.log(client.clientConfig.projectId)

After:

import {createClient} from '@sanity/client'
const client = createClient()

console.log(client.config().projectId)

client.getUrl() is removed

Before:

import createClient from '@sanity/client'
const client = createClient({projectId: 'abc123'})

console.log(client.getUrl('/foo/bar') === 'https://abc123.api.sanity.io/v1/foo/bar')
console.log(client.getUrl('/foo/bar', true) === 'https://abc123.apicdn.sanity.io/v1/foo/bar')

After:

import {createClient} from '@sanity/client'
const client = createClient({projectId: 'abc123'})

const getUrl = (uri: string, useCdn = false) => {
  const config = client.config()
  const base = useCdn ? config.cdnUrl : config.url
  return `${base}/${uri.replace(/^\//, '')}`
}

console.log(getUrl('/foo/bar') === 'https://abc123.api.sanity.io/v1/foo/bar')
console.log(getUrl('/foo/bar', true) === 'https://abc123.apicdn.sanity.io/v1/foo/bar')

client.getDataUrl() is removed

Before:

import createClient from '@sanity/client'
const client = createClient({dataset: 'bikeshop'})

console.log(client.getDataUrl('doc') === '/data/doc/bikeshop')
console.log(client.getDataUrl('doc', 'bike-123') === '/data/doc/bikeshop/bike-123')

After:

import {createClient} from '@sanity/client'
const client = createClient({dataset: 'bikeshop'})

const getDataUrl = (operation: string, path?: string) => {
  const {dataset} = client.config()
  const baseUri = `/${operation}/${dataset}`
  const uri = path ? `${baseUri}/${path}` : baseUri
  return `/data${uri}`.replace(/\/($|\?)/, '$1')
}

console.log(getDataUrl('doc') === '/data/doc/bikeshop')
console.log(getDataUrl('doc', 'bike-123') === '/data/doc/bikeshop/bike-123')

client.isPromiseAPI() is removed, replace with an instanceof check

Before:

import createClient from '@sanity/client'
const client = createClient()

console.log(client.isPromiseAPI())
console.log(client.clientConfig.isPromiseAPI)
console.log(client.config().isPromiseAPI)

After:

import {createClient, SanityClient} from '@sanity/client'
const client = createClient()

console.log(client instanceof SanityClient)

client.observable.isObservableAPI() is removed, replace with an instanceof check

Before:

import createClient from '@sanity/client'
const client = createClient()

console.log(client.observable.isObservableAPI())

After:

import {createClient, ObservableSanityClient} from '@sanity/client'
const client = createClient()

console.log(client.observable instanceof ObservableSanityClient)

client._requestObservable is removed, replace with client.observable.request

Before:

import createClient from '@sanity/client'
const client = createClient()

client._requestObservable({uri: '/ping'}).subscribe()

After:

import {createClient} from '@sanity/client'
const client = createClient()

client.observable.request({uri: '/ping'}).subscribe()

client._dataRequest is removed, replace with client.dataRequest

Before:

import createClient from '@sanity/client'
const client = createClient()

client._dataRequest(endpoint, body, options)

After:

import {createClient} from '@sanity/client'
const client = createClient()

client.dataRequest(endpoint, body, options)

client._create_ is removed, replace with one of client.create, client.createIfNotExists or client.createOrReplace

Before:

import createClient from '@sanity/client'
const client = createClient()

client._create(doc, 'create', options)
client._create(doc, 'createIfNotExists', options)
client._create(doc, 'createOrReplace', options)

After:

import {createClient} from '@sanity/client'
const client = createClient()

client.create(doc, options)
client.createIfNotExists(doc, options)
client.createOrReplace(doc, options)