Optimistic locking
Preventing lost updates with version-based concurrency control
Navixy Repository API is a work in progress. This documentation is published for preview purposes only and doesn't reflect a stable release. Structure, field names, and behaviors are subject to change.
Navixy Repository API uses a version field for optimistic concurrency control, preventing lost updates when multiple clients simultaneously edit the same entity.
How optimistic locking works
Every versioned entity includes a version field:
type Device {
id: ID!
version: Int!
name: String!
# ...
}The version starts at 1 when an entity is created and increments with each successful update.
Update and delete mutations require the current version:
input UpdateDeviceInput {
id: UUID!
version: Int! # Required
title: String
# ...
}
input DeleteDeviceInput {
id: UUID!
version: Int! # Required
}Supported entities
Optimistic locking applies to:
GPS trackers, sensors, beacons
Vehicles, equipment, employees
Asset collections
Geofences, POIs, routes
Work hours, maintenance windows
Warehouse records
Organization hierarchy nodes
User accounts
Organization memberships
External system integrations
Custom field metadata
All catalog items (device types, asset types, roles, tags, etc.)
Operations by type
Create
Not required
Returns version: 1
Update
Required
Returns CONFLICT error on mismatch
Delete
Required
Returns CONFLICT error on mismatch
Workflow
Here's the typical flow for updating an entity:
Query returns version:
Mutation requires version in input:
Handling conflicts
If the entity was modified since you fetched it, the API returns a CONFLICT error. The HTTP status code will be 200 because the request was successfully received and processed — the "conflict" is a business logic outcome, not a transport failure. This follows the GraphQL-over-HTTP specification, which reserves HTTP error codes (4xx, 5xx) for transport-level problems like authentication failures or malformed requests.
The actual error details are in the response body:
Use extensions.code for programmatic error handling, not HTTP status codes. See Error handling for more details.
When you receive a conflict error:
Fetch the entity again to see what changed
Merge the other user's changes with yours if needed
Retry your update with the new version
Concurrent editing example
Here's what happens when two users edit the same device:

User A's update succeeds first. User B's update fails because the version changed. After refetching, User B can successfully update with the current version.
Idempotent commands
Mutations that manage relationships and assignments are called idempotent commands. They do not require or check the version field.
Link device to inventory
Unlink device from inventory
Add identifier to device
Remove identifier from device
Add asset to group
Remove asset from group
Assign role to actor
Revoke role from actor
Grant permission to role
Revoke permission from role
Set user scope restriction
Remove user scope restriction
These operations behave as follows:
Calling
link/assign/grant/addwhen the relationship already exists returns success without making changesCalling
unlink/revoke/removewhen the relationship doesn't exist returns success without making changes
This design simplifies client code. You can safely retry these operations without worrying about conflicts or checking the current state first.
Best practices
Always include the version in your queries. When fetching entities you plan to modify, request the
versionfield so you have it ready for mutations.Handle conflicts gracefully. In collaborative applications, version conflicts are expected. Implement retry logic or prompt users to review changes.
Don't cache versions long-term. Versions can change at any time. Always use the version from your most recent fetch of the entity.
Consider user experience. When a conflict occurs, show users what changed and let them decide how to proceed rather than silently retrying.
Last updated
Was this helpful?