# Optimistic locking

{% hint style="warning" %}
**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.
{% endhint %}

Navixy Repository API uses the optional `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:

```graphql
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:

```graphql
input UpdateDeviceInput {
  id: ID!
  version: Int!   # Optional parameter. Include to enable conflict detection.
  title: String
  # ...
}

input DeleteDeviceInput {
  id: ID!
  version: Int!   # Optional parameter. Include to enable conflict detection.
}
```

### Supported entities

Optimistic locking applies to:

<table><thead><tr><th width="180">Entity</th><th>Description</th></tr></thead><tbody><tr><td><a href="../core-api-reference/devices/types#device">Device</a></td><td>GPS trackers, sensors, beacons</td></tr><tr><td><a href="../core-api-reference/assets/types#asset">Asset</a></td><td>Vehicles, equipment, employees</td></tr><tr><td><a href="../core-api-reference/assets/groups/types#assetgroup">AssetGroup</a></td><td>Asset collections</td></tr><tr><td><a href="../core-api-reference/geo-objects/types#geoobject">Geo object</a></td><td>Geofences, POIs, routes</td></tr><tr><td><a href="../core-api-reference/schedules#schedule">Schedule</a></td><td>Work hours, maintenance windows</td></tr><tr><td><a href="../core-api-reference/devices/inventory#inventory-2">Inventory</a></td><td>Warehouse records</td></tr><tr><td><a href="../core-api-reference/organizations#organization-2">Organization</a></td><td>Organization hierarchy nodes</td></tr><tr><td><a href="../core-api-reference/actors/users#user">User</a></td><td>User accounts</td></tr><tr><td><a href="../core-api-reference/organizations/members#member-2">Member</a></td><td>Organization memberships</td></tr><tr><td><a href="../core-api-reference/actors/integrations#integration-2">Integration</a></td><td>External system integrations</td></tr><tr><td><a href="../core-api-reference/catalogs/catalog-items#catalogitem">CatalogItem</a></td><td>All catalog items (device types, asset types, roles, tags, etc.)</td></tr></tbody></table>

### Operations by type

<table><thead><tr><th width="207.5556640625">Operation</th><th>Behavior</th></tr></thead><tbody><tr><td>Create</td><td>Version not applicable. Returns <code>version: 1</code></td></tr><tr><td>Update with <code>version</code></td><td>Returns <a href="../error-handling#version-conflict-409">409 Conflict</a> if the entity was modified since your last fetch</td></tr><tr><td>Update without <code>version</code></td><td>Applies unconditionally. Silently overwrites concurrent changes</td></tr><tr><td>Delete with <code>version</code></td><td>Returns <a href="../error-handling#version-conflict-409">409 Conflict</a> if the entity was modified since your last fetch</td></tr><tr><td>Delete without <code>version</code></td><td>Deletes unconditionally, regardless of any concurrent changes</td></tr></tbody></table>

{% hint style="warning" %}
Omitting `version` on a delete-type operation is particularly risky, as the operation proceeds unconditionally regardless of what happened to the entity since you last fetched it. Always include `version` when deleting unless you have a specific reason not to.
{% endhint %}

### Workflow

Here's the typical flow for updating an entity:

1. Query returns version:

```graphql
query {
  device(id: "550e8400-e29b-41d4-a716-446655440001") {
    id
    version    # Returns 5
    title
  }
}
```

2. Mutation requires version in input:

```graphql
mutation {
  updateDevice(input: {
    id: "550e8400-e29b-41d4-a716-446655440001"
    version: 5          # Must match the current version
    title: "New name"
  }) {
    device {
      id
      version          # Returns 6 (incremented)
      title
    }
  }
}
```

### Handling conflicts

If you provided `version` and the entity was modified since you fetched it, the API returns a [409 Conflict](https://www.navixy.com/docs/navixy-repository-api/error-handling#version-conflict-409) error. The HTTP status code will be 200 because the request was successfully received and processed — this is a business logic issue, not a transport failure. This follows the [GraphQL-over-HTTP specification](https://graphql.github.io/graphql-over-http/draft/), 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:

```json
{
  "errors": [{
    "message": "Conflict: entity was modified by another request",
    "path": ["deviceUpdate"],
    "extensions": {
      "type": "https://api.navixy.com/errors/conflict",
      "title": "Optimistic Lock Conflict",
      "status": 409,
      "detail": "Device was modified. Expected version 5, current version 6",
      "code": "CONFLICT",
      "entityType": "Device",
      "entityId": "550e8400-e29b-41d4-a716-446655440001",
      "expectedVersion": 5,
      "currentVersion": 6,
      "traceId": "2cf7651916cd43dd8448eb211c80319e"
    }
  }],
  "data": null
}
```

Use `extensions.code` for programmatic error handling, not HTTP status codes. See [Error handling ](https://www.navixy.com/docs/navixy-repository-api/error-handling)for more details.

When you receive a conflict error:

1. Fetch the entity again to see what changed
2. Merge the other user's changes with yours if needed
3. Retry your update with the new version

### Concurrent editing example

Here's what happens when two users edit the same device:

<figure><img src="https://2533405873-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeaPUYn5ATRhY1fALtOkq%2Fuploads%2Fgit-blob-49cd5411731579e50ad5780711a37b9c955d4c50%2Fconcurrent_editing_diagram_concetpt.png?alt=media" alt=""><figcaption></figcaption></figure>

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 don't require or check the `version` field.

| Mutation                                                                                                                                    | Purpose                       |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| [deviceInventoryLink](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/devices/inventory#deviceinventorylink)           | Link device to inventory      |
| [deviceInventoryUnlink](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/devices/inventory#deviceinventoryunlink)       | Unlink device from inventory  |
| [deviceIdentifierAdd](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/devices/mutations#deviceidentifieradd)           | Add identifier to device      |
| [deviceIdentifierRemove](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/devices/mutations#deviceidentifierremove)     | Remove identifier from device |
| [assetGroupItemsAdd](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/assets/groups/mutations#assetgroupitemsadd)       | Add asset to group            |
| [assetGroupItemsRemove](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/assets/groups/mutations#assetgroupitemsremove) | Remove asset from group       |
| [roleAssign](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/access-control/mutations#roleassign)                      | Assign role to actor          |
| [roleRevoke](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/access-control/mutations#rolerevoke)                      | Revoke role from actor        |
| [permissionGrant](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/access-control/mutations#permissiongrant)            | Grant permission to role      |
| [permissionRevoke](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/access-control/mutations#permissionrevoke)          | Revoke permission from role   |
| [userScopeSet](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/access-control/mutations#userscopeset)                  | Set user scope restriction    |
| [userScopeRemove](https://www.navixy.com/docs/navixy-repository-api/core-api-reference/access-control/mutations#userscoperemove)            | Remove user scope restriction |

These operations behave as follows:

* Calling `link`/`assign`/`grant`/`add` when the relationship already exists returns success without making changes
* Calling `unlink`/`revoke`/`remove` when 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

1. **Always include `version` in your queries.** When fetching entities you plan to modify, request the `version` field so you have it ready for mutations.
2. **Always include `version` in updates and deletes.** The field is optional, but omitting it removes your protection against overwriting changes made by other users since your last fetch. Omit it only for programmatic bulk operations where stale-read conflicts are not a concern.
3. **Be especially careful when deleting without `version`.** Unlike unprotected updates, which still write a valid version to the database, unprotected deletes can be irreversible.
4. **Handle conflicts gracefully.** In collaborative applications, version conflicts are expected. Implement retry logic or prompt users to review changes.
5. **Don't cache versions long-term.** Versions can change at any time. Always use the version from your most recent fetch of the entity.
