diff --git a/README.md b/README.md index 94f2e622..841bbef7 100644 --- a/README.md +++ b/README.md @@ -215,3 +215,99 @@ The above listener will receive a `CursorUpdate` event: "data": { "color": "red" } } ``` + +### Component Locking + +Use the Component Locking API to lock stateful components whilst being edited by members to reduce the chances of conflicting changes being made. + +Locks are identified using a unique string, and the Spaces SDK maintains that at most one member holds a lock with a given string at any given time. + +The Component Locking API supports four operations: Query, Acquire, Release, and Subscribe. + +### Query + +`space.locks.get` is used to query whether a lock identifier is currently locked and by whom. It returns a `Lock` type which has the following fields: + +```typescript +type Lock = { + member: SpaceMember; + request: LockRequest; +}; +``` + +For example: + +```javascript +// check if the id is locked +const isLocked = space.locks.get(id) !== undefined; + +// check which member has the lock +const { member } = space.locks.get(id); + +// check the lock attributes assigned by the member holding the lock +const { request } = space.locks.get(id); +const value = request.attributes.get(key); +``` + +`space.locks.getAll` returns all lock identifiers which are currently locked as an array of `Lock`: + +```javascript +const allLocks = space.locks.getAll(); + +for (const lock of allLocks) { + // ... +} +``` + +### Acquire + +`space.locks.acquire` initialises a lock request, adds it to the `locks` field of presence message extras, and calls `presence.update`. + +It returns a Promise which resolves once `presence.update` resolves. + +```javascript +const req = await space.locks.acquire(id); + +// or with some attributes +const attributes = new Map(); +attributes.set('key', 'value'); +const req = await space.locks.acquire(id, { attributes }); +``` + +It throws an error if a lock request already exists for the given identifier with a status of `PENDING` or `LOCKED`. + +### Release + +`space.locks.release` releases a previously requested lock by removing it from the `locks` field of presence message extras, and calls `presence.update`. + +It returns a Promise which resolves once `presence.update` resolves. + +```javascript +await space.locks.release(id); +``` + +### Subscribe + +`space.locks.subscribe` subscribes to changes in lock status across all members. + +The callback is called with a value of type `Lock`. + +```javascript +space.locks.subscribe('update', (lock) => { + // lock.member is the requesting member + // lock.request is the request made by the member +}); + +// or with destructuring: +space.locks.subscribe('update', ({ member, request }) => { + // request.status is the status of the request, one of PENDING, LOCKED, or UNLOCKED + // request.reason is an ErrorInfo if the status is UNLOCKED +}); +``` + +Such changes occur when: + +- a `PENDING` request transitions to `LOCKED` (i.e. the requesting member now holds the lock) +- a `PENDING` request transitions to `UNLOCKED` (i.e. the requesting member does not hold the lock since another member already does) +- a `LOCKED` request transitions to `UNLOCKED` (i.e. the lock was either released or invalidated by a concurrent request which took precedence) +- an `UNLOCKED` request transitions to `LOCKED` (i.e. the requesting member reacquired a lock)