Skip to content
This page View Markdown Open in ChatGPT Open in Claude

Optimistic concurrency control

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 and replace by id 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

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
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 animates the whole exchange.