# Optimistic Concurrency Control

> Stop lost updates with one schema flag - isConcurrencyControlField makes update by id and replace by id compare the version sent with the version stored, and refuse a stale write with a 400.

Source: https://docs.apimaker.dev/v1/docs/features/optimistic-concurrency-control.html

Two people open the same row. Both save. Without a check the second save silently erases the first one. With a version field the second save is refused, and the app can reload and ask.

| | |
|---|---|
| Schema | One field with `isConcurrencyControlField: true`, usually a number stamped by a `conversionFun`. |
| Checked by | [Update by id](https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html) and [replace by id](https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-replace-by-id-api.html) of the schema APIs. Not update many. |
| Refused with | `400` : `Concurrency version mismatch in 'version'. This row/document is already updated.` |
| From code | `g.sys.db.updateById(…)` skips the check unless you pass `skipConcurrencyControl: false`. |

## 1. Add the version field

```typescript
import { EType, ISchemaType, ISchemaProperty } from 'types';

const schema: ISchemaType = {
    _id: EType.objectId,
    name: EType.string,
    version: <ISchemaProperty>{
        __type: EType.number,
        conversions: { conversionFun: () => new Date().getTime() },   // a new version on every save and update
        isConcurrencyControlField: true,
    },
};
module.exports = { schema };
```

## 2. Send it back with every update

**Read, then update with the version you read**

```text
GET  /api/schema/admin/mongodb/shop/products/get-by-id/68d...a1
→ { "name": "Mug", "price": 20, "version": 1727683200850 }

PUT  /api/schema/admin/mongodb/shop/products/update-by-id/68d...a1
{ "price": 22, "version": 1727683200850 }
→ 200, the row now carries a new version
```

- The value sent must equal the value stored. Another write in between changed it : the update answers `400` with the message above, and nothing is written. Reload the row and try again with its new version.
- The field is mandatory in the payload once the row has a version : an update without it is refused the same way.

## From your code

- Calls made with `g.sys.db` skip the check by default, so a scheduler or an event can update rows without carrying versions. Pass `skipConcurrencyControl: false` in the parameters to enforce it there too.
- The [website](https://apimaker.dev/schema-validation) animates the whole exchange.

## Related

- [Table schema](https://docs.apimaker.dev/v1/docs/schema/schema.html) · [Update by id](https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html) · [Error codes](https://docs.apimaker.dev/v1/docs/apis-all/error-codes.html)
