4

We are making a system where users can submit orders. Orders have an incrementing property deliveryNumber; a new order should get a deliveryNumber that is one higher than the previous order.

Is there any built-in support for this in Sanity?
If not, how can we achieve this, without risking collisions due to race conditions?

5 Answers 5

3

I just rediscovered this question and thought I should update it, as there has (for some time now) been a way to do exactly what you're asking:

Here's an example using the Sanity Javascript client:

const sanityClient = require('@sanity/client')
const client = sanityClient({
  projectId: 'my-project-id',
  dataset: 'my-dataset',
  token: 'sanity-auth-token',
  useCdn: false
})

client
  .patch('order-123') // Document ID to patch
  .inc({deliveryNumber: 1}) // Increment field by 1
  .commit() // Perform patch and return a promise
  .then(updatedOrder => {
    console.log('Order delivery number', updatedOrder.deliveryNumber)
  })
  .catch(err => {
    console.error('Ouch. Update failed: ', err.message)
  })

If you're not accessing Sanity via Javascript, you can do the same thing using the HTTP API.

1
  • This approach is still dangerous as it does not protect from race conditions. Better to keep this value in the slug.current field type - so it auto checks and don't allow to have same values
    – Oleg Pro
    Feb 11, 2022 at 17:35
2

As far as i know, there is no built in support for incrementing properties for each new document yet. However, you can achieve it on your own for documents with only one incrementing property (or if the incrementing properties can all be deduced from a single, incrementing property).

Method

To do this we take advantage of the fact that when you create a new document, you can set the _id instead of letting Sanity generate it for you. If you try to create a document with an _id that is already used for another document, the query will fail. We use this as a mechanism to guard against race conditions.

For the use case in quesition, we need to do three things:

  1. Fetch the deliveryNumber of the last created order (see this answer)
  2. Increment by one to get the next deliveryNumber
  3. Create a new order with the same value for _id and deliveryNumber

If two orders are placed at the same time, so that there is an attempt to create two orders with the same deliveryNumber, the last query that is processed by Sanity will fail because is has the same _id as the first one. As long as it doesn't fail, every order should have a uniqe deliveryNumber one higher than the previos one.

When it fails

If the orders are created as a result of user interaction, I suggest letting the user know that it failed and ask them to try again. This will not happen often, and almost certainly not twice in a row.

If, however, you must programatically ensure that you try again until you succeed, I suggest backing off a randomized amount of time (that increases exponentially each time it fails), before trying again.

Viability

The more frequently orders are created, the less viable this solution is. For most systems I assume this is unlikely to fail at all.

0

Now, it's possible to have initialValues in the schema and that allows setting an automatic incrementing value in the schema.

There is a post to the feature and how it works.

Auto-incrementing value

Let's use the example of the question, you want to have a delivery number which is an incrementing number.

So count every order document and increment it by one to use it as initialValue for the new document.

Schema order.js

import client from 'part:@sanity/base/client';

export default {
  title: "Order",
  name: "order",
  type: "document",
  initialValue: async () => ({
    deliveryNumber: (await client.fetch(`//groq
       count(*[_type == "order"])
    }) + 1
  }),
  fields: [
    {
      title: "Delivery number",
      name: "deliveryNumber",
      type: "number"
    },
    // other fields
  ]
}

Not sure if there is an easier way but it's working. It would be great if we could have a helper - something like autoIncrement()

A possible implementation:

const autoIncrement = async type = (await client.fetch(`//groq
  count(*[_type == "${type}"])
}) + 1

// usage:
{
  // ...,
  initialValue: async () => ({
        deliveryNumber: await autoIncrement("order")
  }),
  // ....
}

Note: It would be nice if that would work with-out async/await and with-out passing the type to autoIncrement but I'm not sure if that's possible.

1
  • 2
    It should be noted that initialValue only works in the Studio when the user creates a new document using the UI. So if one plan to do it via the API, then this answer is the way to go.
    – knut
    Nov 23, 2019 at 19:17
0

after reading all the answers above, I come with my simple solution to automate giving document numbers.

First I create a field for the document number like this

// ...
{
  name: 'docnum',
  title: 'Document Number',
  type: 'number',
},
// ...

then I create initialValue helper at bottom

import sanityClient from 'part:@sanity/base/client';

// ...

initialValue: async () => {
  const query = '*[_type == "documentName"] | order(_createdAt desc){docnum}';
  const result = await sanityClient.fetch(query);

  if (result.length > 0) {
    return { docnum: hasil[0].docnum + 1 };
  } else {
    return { docnum: 1 };
  }
},
// ...

with this approach, my first document will have initial value docnum 1. And later document will have initial value of last + 1. I'm using newest version btw (1.150.2)

0

I wanted to make an auto random id generating field for every new document. I tried all above mentioned methods but non work for me. My sanity version says fetch is depreciated do not use. And it always return an object with value 1 { docnum: 1 }.

Then I found this trick working for me.

  {
      name: "docid",
      title: "ID",
      type: "number",
      initialValue: () => {
        let d = new Date();
        return d.getTime();
      },
   }

It will always return a random and unique number. It is simple little trick for those who wanted a field like this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.