diff --git a/examples/spaces-component-locking/javascript/.env.example b/examples/spaces-component-locking/javascript/.env.example
new file mode 100644
index 0000000000..acd79e16f2
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/.env.example
@@ -0,0 +1 @@
+VITE_PUBLIC_ABLY_KEY=
\ No newline at end of file
diff --git a/examples/spaces-component-locking/javascript/.gitignore b/examples/spaces-component-locking/javascript/.gitignore
new file mode 100644
index 0000000000..fd3dbb571a
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/examples/spaces-component-locking/javascript/README.md b/examples/spaces-component-locking/javascript/README.md
new file mode 100644
index 0000000000..6f25042803
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/README.md
@@ -0,0 +1,39 @@
+# Locking components within collaborative applications
+
+This folder contains the code for the component locking (Typescript) - a demo of how you can leverage [Ably Spaces](https://github.com/ably/spaces) to lock components within a form or web page.
+
+## Getting started
+
+1. Clone the [Ably docs](https://github.com/ably/docs) repository where this example can be found:
+
+```sh
+git clone git@github.com:ably/docs.git
+```
+
+2. Change directory:
+
+```sh
+cd /examples/spaces-component-locking/javascript/
+```
+
+3. Rename the environment file:
+
+```sh
+mv .env.example .env.local
+```
+
+4. In `.env.local` update the value of `VITE_PUBLIC_ABLY_KEY` to be your Ably API key.
+
+5. Install dependencies:
+
+```sh
+yarn install
+```
+
+6. Run the server:
+
+```sh
+yarn run dev
+```
+
+7. Try it out by opening two tabs to [http://localhost:5173/](http://localhost:5173/) with your browser to see the result.
diff --git a/examples/spaces-component-locking/javascript/index.html b/examples/spaces-component-locking/javascript/index.html
new file mode 100644
index 0000000000..d1022daaf5
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Component locking
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/spaces-component-locking/javascript/package.json b/examples/spaces-component-locking/javascript/package.json
new file mode 100644
index 0000000000..936e2a0ae5
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "js-component-locking",
+ "version": "1.0.0",
+ "main": "index.js",
+ "license": "MIT",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "dotenv": "^16.4.5",
+ "typescript": "^5.5.4",
+ "@faker-js/faker": "^9.2.0"
+ },
+ "dependencies": {
+ "@ably/spaces": "^0.4.0",
+ "ably": "^2.3.1",
+ "nanoid": "^5.0.7",
+ "vite": "^5.4.2"
+ }
+}
diff --git a/examples/spaces-component-locking/javascript/src/LockedField.ts b/examples/spaces-component-locking/javascript/src/LockedField.ts
new file mode 100644
index 0000000000..129742c17c
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/src/LockedField.ts
@@ -0,0 +1,18 @@
+export const createLockedFieldSvg = (className: string): SVGElement => {
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute('width', '1em');
+ svg.setAttribute('height', '1em');
+ svg.setAttribute('viewBox', '0 0 16 16');
+ svg.setAttribute('fill', 'none');
+ svg.setAttribute('class', className);
+
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ path.setAttribute(
+ 'd',
+ 'M12.0003 5.8334H11.3337V4.50007C11.3337 2.66008 9.84032 1.16675 8.00033 1.16675C6.16033 1.16675 4.66699 2.66008 4.66699 4.50007V5.8334H4.00033C3.26699 5.8334 2.66699 6.43339 2.66699 7.16672V13.8334C2.66699 14.5667 3.26699 15.1667 4.00033 15.1667H12.0003C12.7337 15.1667 13.3337 14.5667 13.3337 13.8334V7.16672C13.3337 6.43339 12.7337 5.8334 12.0003 5.8334ZM8.00033 11.8334C7.26699 11.8334 6.66699 11.2334 6.66699 10.5C6.66699 9.76671 7.26699 9.16671 8.00033 9.16671C8.73366 9.16671 9.33366 9.76671 9.33366 10.5C9.33366 11.2334 8.73366 11.8334 8.00033 11.8334ZM6.00033 5.8334V4.50007C6.00033 3.39341 6.89366 2.50008 8.00033 2.50008C9.10699 2.50008 10.0003 3.39341 10.0003 4.50007V5.8334H6.00033Z',
+ );
+ path.setAttribute('fill', 'currentColor');
+
+ svg.appendChild(path);
+ return svg;
+};
diff --git a/examples/spaces-component-locking/javascript/src/script.ts b/examples/spaces-component-locking/javascript/src/script.ts
new file mode 100644
index 0000000000..7bee39b8ab
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/src/script.ts
@@ -0,0 +1,184 @@
+import Spaces, { Space, Lock, type SpaceMember } from '@ably/spaces';
+import { Realtime } from 'ably';
+import { nanoid } from 'nanoid';
+import { createLockedFieldSvg } from './LockedField';
+import { faker } from '@faker-js/faker';
+
+type Member = Omit & {
+ profileData: { memberColor: string; memberName: string };
+};
+
+interface Entry {
+ label: string;
+ name: string;
+}
+let space: Space;
+const entries: Entry[] = [
+ { label: 'Entry 1', name: 'entry1' },
+ { label: 'Entry 2', name: 'entry2' },
+ { label: 'Entry 3', name: 'entry3' },
+];
+
+const client = new Realtime({
+ clientId: nanoid(),
+ key: import.meta.env.VITE_PUBLIC_ABLY_KEY as string,
+});
+
+connect();
+
+async function connect() {
+ buildForm();
+
+ const spaces = new Spaces(client);
+ space = await spaces.get('component-locking');
+
+ // /** 💡 Enter the space as soon as it's available 💡 */
+ await space.enter({
+ memberName: faker.person.fullName(),
+ memberColor: faker.color.rgb({ format: 'hex', casing: 'lower' }),
+ });
+
+ const locks = await space.locks.getAll();
+
+ if (locks.length > 0) {
+ locks.map(async (lock) => {
+ await updateComponent(lock);
+ });
+ }
+
+ // /** 💡 Subscribe to all component updates 💡 */
+ space.locks.subscribe('update', async (componentUpdate) => {
+ // /** 💡 Update form on each components update 💡 */
+ console.log(componentUpdate);
+ await updateComponent(componentUpdate);
+ });
+}
+
+async function updateComponent(componentUpdate: Lock) {
+ const self = await space.members.getSelf();
+ const locked = componentUpdate?.status === 'locked';
+
+ const inputElement = document.getElementById(componentUpdate.id) as HTMLInputElement;
+ const parentDiv = inputElement.closest('.input-cell-container');
+ const inputContainer = parentDiv?.querySelector('.input-container');
+
+ if (locked) {
+ const lockHolder = componentUpdate.member as Member;
+ const lockedByYou = locked && lockHolder?.connectionId === self?.connectionId;
+ const readOnly = Boolean(lockHolder && !lockedByYou);
+
+ const memberColor = lockHolder?.profileData.memberColor;
+ const memberName = lockedByYou ? 'You' : lockHolder?.profileData.memberName;
+
+ const lockedDiv = document.createElement('div');
+ lockedDiv.className = 'lock';
+ lockedDiv.id = 'lock';
+ lockedDiv.innerHTML = `${memberName} ${lockedByYou ? '' : createLockedFieldSvg('text-base').outerHTML}`;
+ lockedDiv.style.setProperty('--member-bg-color', memberColor);
+ inputContainer?.appendChild(lockedDiv);
+
+ inputElement.style.setProperty('--member-bg-color', memberColor);
+ inputElement.setAttribute('data-locked', 'true');
+
+ if (lockHolder) {
+ inputElement.classList.remove('regular-cell');
+ inputElement.classList.add('active-cell');
+ } else {
+ inputElement.classList.add('locked');
+ }
+
+ if (readOnly) {
+ inputElement.classList.remove('full-access');
+ inputElement.classList.add('read-only');
+ }
+ } else {
+ const lockedDiv = inputContainer?.querySelector('.lock');
+
+ if (lockedDiv) {
+ inputContainer?.removeChild(lockedDiv);
+ }
+ inputElement.removeAttribute('data-locked');
+ inputElement.classList.remove('locked', 'read-only', 'active-cell');
+ inputElement.classList.add('regular-cell', 'full-access');
+ inputElement.style.removeProperty('--member-bg-color');
+ }
+}
+
+const handleFocus = async (event: FocusEvent) => {
+ const focusedElement = event.target as HTMLInputElement;
+ const currentlyLockedElement = document.querySelector('[data-locked="true"]') as HTMLInputElement;
+
+ if (currentlyLockedElement) {
+ await space?.locks.release(currentlyLockedElement.name);
+ }
+
+ if (focusedElement.getAttribute('data-locked') === 'true') {
+ return;
+ }
+
+ await space?.locks.acquire(focusedElement.name);
+};
+
+const handleBlur = async (event: FocusEvent) => {
+ const focusedElement = event.target as HTMLInputElement;
+
+ if (focusedElement.getAttribute('data-locked') !== 'true') {
+ return;
+ }
+
+ if (event.relatedTarget) {
+ space?.locks.release(focusedElement.name);
+ }
+};
+
+function buildForm() {
+ const innerContainer = document.getElementById('inner');
+
+ entries.map((entry) => {
+ const inputCellContainer = document.createElement('div');
+ inputCellContainer.id = 'input-cell-container';
+ inputCellContainer.className = 'input-cell-container';
+ inputCellContainer.style.setProperty('--member-bg-color', '#AC8600');
+ const entryLabel = document.createElement('label');
+ entryLabel.className = 'label';
+ entryLabel.setAttribute('for', entry.name);
+ entryLabel.textContent = entry.label;
+
+ const inputContainer = document.createElement('div');
+ inputContainer.className = 'input-container';
+ inputContainer.id = 'input-container';
+
+ const formInput = document.createElement('input');
+ formInput.id = entry.name;
+ formInput.className = `input regular-cell full-access`;
+ formInput.placeholder = 'Click to lock and edit me';
+ formInput.name = entry.name;
+ formInput.onfocus = (event) => {
+ handleFocus(event);
+ };
+ formInput.onblur = (event) => {
+ handleBlur(event);
+ };
+
+ innerContainer?.appendChild(inputCellContainer);
+ inputCellContainer.appendChild(entryLabel);
+ inputCellContainer?.appendChild(inputContainer);
+ inputContainer?.appendChild(formInput);
+
+ const channel = client.channels.get(`component-locking-${entry.name}`);
+
+ channel.subscribe('update', (message) => {
+ const input = document.getElementById(entry.name) as HTMLInputElement;
+ if (input) {
+ input.value = message.data.value;
+ }
+ });
+
+ formInput.addEventListener('input', (event) => {
+ const target = event.target as HTMLInputElement;
+ channel.publish('update', {
+ value: target.value,
+ });
+ });
+ });
+}
diff --git a/examples/spaces-component-locking/javascript/src/styles.css b/examples/spaces-component-locking/javascript/src/styles.css
new file mode 100644
index 0000000000..7b1e413348
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/src/styles.css
@@ -0,0 +1,215 @@
+:root {
+ --max-width: 1100px;
+ --border-radius: 12px;
+ --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
+ "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
+ "Fira Mono", "Droid Sans Mono", "Courier New", monospace;
+
+ --foreground-rgb: 0, 0, 0;
+ --background-start-rgb: 214, 219, 220;
+ --background-end-rgb: 255, 255, 255;
+
+ --primary-glow: conic-gradient(
+ from 180deg at 50% 50%,
+ #16abff33 0deg,
+ #0885ff33 55deg,
+ #54d6ff33 120deg,
+ #0071ff33 160deg,
+ transparent 360deg
+ );
+ --secondary-glow: radial-gradient(
+ rgba(255, 255, 255, 1),
+ rgba(255, 255, 255, 0)
+ );
+
+ --tile-start-rgb: 239, 245, 249;
+ --tile-end-rgb: 228, 232, 233;
+ --tile-border: conic-gradient(
+ #00000080,
+ #00000040,
+ #00000030,
+ #00000020,
+ #00000010,
+ #00000010,
+ #00000080
+ );
+
+ --callout-rgb: 238, 240, 241;
+ --callout-border-rgb: 172, 175, 176;
+ --card-rgb: 180, 185, 188;
+ --card-border-rgb: 131, 134, 135;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --foreground-rgb: 255, 255, 255;
+ --background-start-rgb: 0, 0, 0;
+ --background-end-rgb: 0, 0, 0;
+
+ --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
+ --secondary-glow: linear-gradient(
+ to bottom right,
+ rgba(1, 65, 255, 0),
+ rgba(1, 65, 255, 0),
+ rgba(1, 65, 255, 0.3)
+ );
+
+ --tile-start-rgb: 2, 13, 46;
+ --tile-end-rgb: 2, 5, 19;
+ --tile-border: conic-gradient(
+ #ffffff80,
+ #ffffff40,
+ #ffffff30,
+ #ffffff20,
+ #ffffff10,
+ #ffffff10,
+ #ffffff80
+ );
+
+ --callout-rgb: 20, 20, 20;
+ --callout-border-rgb: 108, 108, 108;
+ --card-rgb: 100, 100, 100;
+ --card-border-rgb: 200, 200, 200;
+ }
+}
+
+* {
+ box-sizing: border-box;
+ padding: 0;
+ margin: 0;
+}
+
+html,
+body {
+ max-width: 100vw;
+ overflow-x: hidden;
+}
+
+body {
+ color: rgb(var(--foreground-rgb));
+ background: linear-gradient(
+ to bottom,
+ transparent,
+ rgb(var(--background-end-rgb))
+ )
+ rgb(var(--background-start-rgb));
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.container {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ background-color: #f4f8fb;
+ height: 100vh;
+}
+
+.inner {
+ width: 100%;
+ max-width: 320px;
+ padding: 1rem 0.5rem;
+}
+
+/* InputCell */
+.input-cell-container {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 1rem;
+}
+
+.input-cell-container:last-child {
+ display: none;
+}
+
+@media (min-height: 400px) {
+ .input-cell-container:last-child {
+ display: flex;
+ }
+}
+
+.input-container {
+ position: relative;
+}
+
+.label {
+ margin-bottom: 0.5rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: black;
+}
+
+.lock {
+ position: absolute;
+ top: -24px;
+ right: 6px;
+ width: auto;
+ height: 24px;
+ padding: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.625rem;
+ background-color: var(--member-bg-color);
+ color: white;
+ border-radius: 4px 4px 0px 0px;
+ z-index: 1;
+ gap: 4px;
+}
+
+.text-base {
+ font-size: 1rem;
+ line-height: 1.5rem;
+}
+
+.input {
+ padding: 0.5rem;
+ width: 100%;
+ height: 2.5rem;
+ font-size: 0.875rem;
+ border-radius: 0.375rem;
+ outline: none;
+ transition: all 0.2s ease-in-out;
+}
+
+.input:hover,
+.input:focus {
+ background-color: white;
+}
+
+.active-cell {
+ border: 2px solid var(--member-bg-color);
+}
+
+.regular-cell {
+ border: 0.25px solid rgb(198, 206, 217);
+ background-color: white;
+}
+
+.regular-cell:hover {
+ border-color: rgb(173, 182, 194);
+}
+
+.full-access {
+ background-color: #edf1f6;
+}
+
+.read-only {
+ background-color: rgb(226, 231, 239);
+ cursor: not-allowed;
+}
+
+.read-only:hover {
+ background-color: rgb(226, 231, 239);
+}
+
+.input.locked {
+ background-color: #f0f0f0;
+ cursor: not-allowed;
+}
diff --git a/examples/spaces-component-locking/javascript/tsconfig.json b/examples/spaces-component-locking/javascript/tsconfig.json
new file mode 100644
index 0000000000..cfcaf10637
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "es2021",
+ "DOM",
+ "DOM.Iterable",
+ ],
+ "module": "ESNext",
+ "target": "ESNext",
+ "strict": true,
+ "esModuleInterop": true,
+ "moduleResolution": "Node",
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowUnusedLabels": false,
+ "allowUnreachableCode": false,
+ "exactOptionalPropertyTypes": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitOverride": true,
+ "noImplicitReturns": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "checkJs": true,
+ // Everything below are custom changes for this app
+ "allowJs": true,
+ "outDir": "dist"
+ },
+ "include": [
+ "./src/**/*",
+ "./__tests__/**/*",
+ "vite-env.d.ts"
+ ]
+}
\ No newline at end of file
diff --git a/examples/spaces-component-locking/javascript/vite-env.d.ts b/examples/spaces-component-locking/javascript/vite-env.d.ts
new file mode 100644
index 0000000000..9454b92dea
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/vite-env.d.ts
@@ -0,0 +1,8 @@
+interface ImportMetaEnv {
+ readonly VITE_PUBLIC_ABLY_KEY: string;
+ // Add other environment variables here if needed
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
diff --git a/examples/spaces-component-locking/javascript/yarn.lock b/examples/spaces-component-locking/javascript/yarn.lock
new file mode 100644
index 0000000000..24c36ebd89
--- /dev/null
+++ b/examples/spaces-component-locking/javascript/yarn.lock
@@ -0,0 +1,583 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ably/msgpack-js@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@ably/msgpack-js/-/msgpack-js-0.4.0.tgz#aaaf5d8dffacf7aa253effd8c3488aa09130e6a2"
+ integrity sha512-IPt/BoiQwCWubqoNik1aw/6M/DleMdrxJOUpSja6xmMRbT2p1TA8oqKWgfZabqzrq8emRNeSl/+4XABPNnW5pQ==
+ dependencies:
+ bops "^1.0.1"
+
+"@ably/spaces@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@ably/spaces/-/spaces-0.4.0.tgz#766d748d79f3de3cd82939431daea99479150bbf"
+ integrity sha512-yehNgkv9DOHUBR1c46/5Q4BhkF3TJkKb00kLDhZFWTVJpFSW0Mkw0PUgHLkzAg9Bt9LJPcA0mgZq3mFeLQCrIg==
+ dependencies:
+ nanoid "^3.3.7"
+
+"@esbuild/aix-ppc64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
+ integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
+
+"@esbuild/android-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
+ integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
+
+"@esbuild/android-arm@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
+ integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
+
+"@esbuild/android-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
+ integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
+
+"@esbuild/darwin-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
+ integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
+
+"@esbuild/darwin-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
+ integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
+
+"@esbuild/freebsd-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
+ integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
+
+"@esbuild/freebsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
+ integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
+
+"@esbuild/linux-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
+ integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
+
+"@esbuild/linux-arm@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
+ integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
+
+"@esbuild/linux-ia32@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
+ integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
+
+"@esbuild/linux-loong64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
+ integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
+
+"@esbuild/linux-mips64el@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
+ integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
+
+"@esbuild/linux-ppc64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
+ integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
+
+"@esbuild/linux-riscv64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
+ integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
+
+"@esbuild/linux-s390x@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
+ integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
+
+"@esbuild/linux-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
+ integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
+
+"@esbuild/netbsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
+ integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
+
+"@esbuild/openbsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
+ integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
+
+"@esbuild/sunos-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
+ integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
+
+"@esbuild/win32-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
+ integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
+
+"@esbuild/win32-ia32@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
+ integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
+
+"@esbuild/win32-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
+ integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
+
+"@faker-js/faker@^9.2.0":
+ version "9.2.0"
+ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.2.0.tgz#269ee3a5d2442e88e10d984e106028422bcb9551"
+ integrity sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==
+
+"@rollup/rollup-android-arm-eabi@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.1.tgz#c3a7938551273a2b72820cf5d22e54cf41dc206e"
+ integrity sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==
+
+"@rollup/rollup-android-arm64@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.1.tgz#fa3693e4674027702c42fcbbb86bbd0c635fd3b9"
+ integrity sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==
+
+"@rollup/rollup-darwin-arm64@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.1.tgz#e19922f4ac1e4552a230ff8f49d5688c5c07d284"
+ integrity sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==
+
+"@rollup/rollup-darwin-x64@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.1.tgz#897f8d47b115ea84692a29cf2366899499d4d915"
+ integrity sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.1.tgz#7d1e2a542f3a5744f5c24320067bd5af99ec9d62"
+ integrity sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==
+
+"@rollup/rollup-linux-arm-musleabihf@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.1.tgz#88bec1c9df85fc5e24d49f783e19934717dd69b5"
+ integrity sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==
+
+"@rollup/rollup-linux-arm64-gnu@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.1.tgz#6dc60f0fe7bd49ed07a2d4d9eab15e671b3bd59d"
+ integrity sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==
+
+"@rollup/rollup-linux-arm64-musl@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.1.tgz#a03b78775c129e8333aca9e1e420e8e217ee99b9"
+ integrity sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==
+
+"@rollup/rollup-linux-powerpc64le-gnu@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.1.tgz#ee3810647faf2c105a5a4e71260bb90b96bf87bc"
+ integrity sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==
+
+"@rollup/rollup-linux-riscv64-gnu@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.1.tgz#385d76a088c27db8054d9f3f28d64d89294f838e"
+ integrity sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==
+
+"@rollup/rollup-linux-s390x-gnu@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.1.tgz#daa2b62a6e6f737ebef6700a12a93c9764e18583"
+ integrity sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==
+
+"@rollup/rollup-linux-x64-gnu@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.1.tgz#790ae96118cc892464e9f10da358c0c8a6b9acdd"
+ integrity sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==
+
+"@rollup/rollup-linux-x64-musl@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.1.tgz#d613147f7ac15fafe2a0b6249e8484e161ca2847"
+ integrity sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==
+
+"@rollup/rollup-win32-arm64-msvc@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.1.tgz#18349db8250559a5460d59eb3575f9781be4ab98"
+ integrity sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==
+
+"@rollup/rollup-win32-ia32-msvc@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.1.tgz#199648b68271f7ab9d023f5c077725d51d12d466"
+ integrity sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==
+
+"@rollup/rollup-win32-x64-msvc@4.21.1":
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.1.tgz#4d3ec02dbf280c20bfeac7e50cd5669b66f9108f"
+ integrity sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==
+
+"@sindresorhus/is@^4.0.0":
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
+ integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
+
+"@szmarczak/http-timer@^4.0.5":
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807"
+ integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==
+ dependencies:
+ defer-to-connect "^2.0.0"
+
+"@types/cacheable-request@^6.0.1":
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183"
+ integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==
+ dependencies:
+ "@types/http-cache-semantics" "*"
+ "@types/keyv" "^3.1.4"
+ "@types/node" "*"
+ "@types/responselike" "^1.0.0"
+
+"@types/estree@1.0.5":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
+ integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
+
+"@types/http-cache-semantics@*":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
+ integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==
+
+"@types/keyv@^3.1.4":
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
+ integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==
+ dependencies:
+ "@types/node" "*"
+
+"@types/node@*":
+ version "22.5.0"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.0.tgz#10f01fe9465166b4cab72e75f60d8b99d019f958"
+ integrity sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==
+ dependencies:
+ undici-types "~6.19.2"
+
+"@types/responselike@^1.0.0":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50"
+ integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==
+ dependencies:
+ "@types/node" "*"
+
+ably@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/ably/-/ably-2.3.1.tgz#c3b02df6aa4262284b0b1ba130e5f85e0f423ef4"
+ integrity sha512-OQ9PUwQe0qehOUPqvLargmUTlPQsxCAdStIpLZE7pT68yDd3JF28R1Ap7C50DluCIa6NqXrieRSa2tcLNN95PA==
+ dependencies:
+ "@ably/msgpack-js" "^0.4.0"
+ fastestsmallesttextencoderdecoder "^1.0.22"
+ got "^11.8.5"
+ ulid "^2.3.0"
+ ws "^8.17.1"
+
+base64-js@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.0.2.tgz#474211c95e6cf2a547db461e4f6778b51d08fa65"
+ integrity sha512-ZXBDPMt/v/8fsIqn+Z5VwrhdR6jVka0bYobHdGia0Nxi7BJ9i/Uvml3AocHIBtIIBhZjBw5MR0aR4ROs/8+SNg==
+
+bops@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/bops/-/bops-1.0.1.tgz#502aaf00ee119db1dbae088e3df4bea2e241dbcc"
+ integrity sha512-qCMBuZKP36tELrrgXpAfM+gHzqa0nLsWZ+L37ncsb8txYlnAoxOPpVp+g7fK0sGkMXfA0wl8uQkESqw3v4HNag==
+ dependencies:
+ base64-js "1.0.2"
+ to-utf8 "0.0.1"
+
+cacheable-lookup@^5.0.3:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
+ integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
+
+cacheable-request@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817"
+ integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^4.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^6.0.1"
+ responselike "^2.0.0"
+
+clone-response@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3"
+ integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==
+ dependencies:
+ mimic-response "^1.0.0"
+
+decompress-response@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
+ integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+ dependencies:
+ mimic-response "^3.1.0"
+
+defer-to-connect@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
+ integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
+
+dotenv@^16.4.5:
+ version "16.4.5"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
+ integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
+
+end-of-stream@^1.1.0:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+esbuild@^0.21.3:
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
+ integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.21.5"
+ "@esbuild/android-arm" "0.21.5"
+ "@esbuild/android-arm64" "0.21.5"
+ "@esbuild/android-x64" "0.21.5"
+ "@esbuild/darwin-arm64" "0.21.5"
+ "@esbuild/darwin-x64" "0.21.5"
+ "@esbuild/freebsd-arm64" "0.21.5"
+ "@esbuild/freebsd-x64" "0.21.5"
+ "@esbuild/linux-arm" "0.21.5"
+ "@esbuild/linux-arm64" "0.21.5"
+ "@esbuild/linux-ia32" "0.21.5"
+ "@esbuild/linux-loong64" "0.21.5"
+ "@esbuild/linux-mips64el" "0.21.5"
+ "@esbuild/linux-ppc64" "0.21.5"
+ "@esbuild/linux-riscv64" "0.21.5"
+ "@esbuild/linux-s390x" "0.21.5"
+ "@esbuild/linux-x64" "0.21.5"
+ "@esbuild/netbsd-x64" "0.21.5"
+ "@esbuild/openbsd-x64" "0.21.5"
+ "@esbuild/sunos-x64" "0.21.5"
+ "@esbuild/win32-arm64" "0.21.5"
+ "@esbuild/win32-ia32" "0.21.5"
+ "@esbuild/win32-x64" "0.21.5"
+
+fastestsmallesttextencoderdecoder@^1.0.22:
+ version "1.0.22"
+ resolved "https://registry.yarnpkg.com/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz#59b47e7b965f45258629cc6c127bf783281c5e93"
+ integrity sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==
+
+fsevents@~2.3.2, fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+get-stream@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+ dependencies:
+ pump "^3.0.0"
+
+got@^11.8.5:
+ version "11.8.6"
+ resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a"
+ integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==
+ dependencies:
+ "@sindresorhus/is" "^4.0.0"
+ "@szmarczak/http-timer" "^4.0.5"
+ "@types/cacheable-request" "^6.0.1"
+ "@types/responselike" "^1.0.0"
+ cacheable-lookup "^5.0.3"
+ cacheable-request "^7.0.2"
+ decompress-response "^6.0.0"
+ http2-wrapper "^1.0.0-beta.5.2"
+ lowercase-keys "^2.0.0"
+ p-cancelable "^2.0.0"
+ responselike "^2.0.0"
+
+http-cache-semantics@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
+ integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
+
+http2-wrapper@^1.0.0-beta.5.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
+ integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==
+ dependencies:
+ quick-lru "^5.1.1"
+ resolve-alpn "^1.0.0"
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+keyv@^4.0.0:
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+lowercase-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
+ integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
+mimic-response@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
+ integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+
+mimic-response@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
+ integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
+nanoid@^3.3.7:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
+
+nanoid@^5.0.7:
+ version "5.0.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6"
+ integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==
+
+normalize-url@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
+ integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
+
+once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+p-cancelable@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf"
+ integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==
+
+picocolors@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
+ integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
+
+postcss@^8.4.41:
+ version "8.4.41"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681"
+ integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==
+ dependencies:
+ nanoid "^3.3.7"
+ picocolors "^1.0.1"
+ source-map-js "^1.2.0"
+
+pump@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+ integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+quick-lru@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
+ integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
+resolve-alpn@^1.0.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
+ integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
+
+responselike@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc"
+ integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==
+ dependencies:
+ lowercase-keys "^2.0.0"
+
+rollup@^4.20.0:
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.1.tgz#65b9b9e9de9a64604fab083fb127f3e9eac2935d"
+ integrity sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==
+ dependencies:
+ "@types/estree" "1.0.5"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.21.1"
+ "@rollup/rollup-android-arm64" "4.21.1"
+ "@rollup/rollup-darwin-arm64" "4.21.1"
+ "@rollup/rollup-darwin-x64" "4.21.1"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.21.1"
+ "@rollup/rollup-linux-arm-musleabihf" "4.21.1"
+ "@rollup/rollup-linux-arm64-gnu" "4.21.1"
+ "@rollup/rollup-linux-arm64-musl" "4.21.1"
+ "@rollup/rollup-linux-powerpc64le-gnu" "4.21.1"
+ "@rollup/rollup-linux-riscv64-gnu" "4.21.1"
+ "@rollup/rollup-linux-s390x-gnu" "4.21.1"
+ "@rollup/rollup-linux-x64-gnu" "4.21.1"
+ "@rollup/rollup-linux-x64-musl" "4.21.1"
+ "@rollup/rollup-win32-arm64-msvc" "4.21.1"
+ "@rollup/rollup-win32-ia32-msvc" "4.21.1"
+ "@rollup/rollup-win32-x64-msvc" "4.21.1"
+ fsevents "~2.3.2"
+
+source-map-js@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
+ integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
+
+to-utf8@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/to-utf8/-/to-utf8-0.0.1.tgz#d17aea72ff2fba39b9e43601be7b3ff72e089852"
+ integrity sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==
+
+typescript@^5.5.4:
+ version "5.5.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
+ integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
+
+ulid@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/ulid/-/ulid-2.3.0.tgz#93063522771a9774121a84d126ecd3eb9804071f"
+ integrity sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==
+
+undici-types@~6.19.2:
+ version "6.19.8"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
+ integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
+
+vite@^5.4.2:
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.2.tgz#8acb6ec4bfab823cdfc1cb2d6c53ed311bc4e47e"
+ integrity sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==
+ dependencies:
+ esbuild "^0.21.3"
+ postcss "^8.4.41"
+ rollup "^4.20.0"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+ws@^8.17.1:
+ version "8.18.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
+ integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
diff --git a/examples/spaces-component-locking/page.md b/examples/spaces-component-locking/page.md
new file mode 100644
index 0000000000..f20c939ac7
--- /dev/null
+++ b/examples/spaces-component-locking/page.md
@@ -0,0 +1,126 @@
+# Locking components within collaborative applications
+
+Enable users to lock components for editing within a collaborative application.
+
+Manage concurrent access to shared resources within your application and prevent conflicts in collaborative environments, ensuring that only one user can modify a specific component at a time.
+
+Component locking enhances applications where multiple users can edit the same fields, such as spreadsheets, slide shows and forms. It is a key component in maintaining data integrity and preventing conflicting changes.
+
+Component locking is implemented using [Ably Spaces](https://ably.com/docs/products/spaces). The Spaces SDK contains a set of purpose-built APIs that abstract away the complexities involved in managing the state of components accessibility in a collaborative application. It is built on top of Ably's core platform, and so it provides the same performance and scaling guarantees.
+
+## Resources
+
+// React brief (Only visible if viewing the React example)
+
+Use the following components to add component locking into an application:
+
+* [`SpacesProvider`](https://ably.com/docs/spaces/react#spaces-provider): initializes and manages a shared space client instance, passing it down through React context to enable realtime spaces functionality across the application.
+* [`SpaceProvider`](https://ably.com/docs/spaces/react#spaces-provider): manages the state and functionality of a specific space, providing access to realtime interactions within that space via React context.
+* [`useSpace()`](https://ably.com/docs/spaces/react#useSpace) hook: a hook to subscribe to the current Space, receive Space state events, and get the current Space instance.
+* [`useMembers()`](https://ably.com/docs/spaces/react#useMembers) hook: a hook to build component locking. It retrieves members of the space, including members that have left the space, but have not yet been removed.
+* [`space.locks.acquire()`](https://ably.com/docs/spaces/locking#acquire): a method to attempt to acquire a lock with a given unique ID.
+
+This example also uses the [`ChannelProvider`](https://ably.com/docs/getting-started/react#channel-provider) and [`useChannel()`](https://ably.com/docs/getting-started/react#useChannel) hook from the core Pub/Sub product in order to publish and subscribe to the changing values of each component.
+
+Find out more about [component locking](https://ably.com/docs/spaces/locking).
+
+// End React brief
+
+// Javascript brief (Only visible if viewing the Javascript example)
+
+Use the following components to add component locking into an application:
+
+* [`spaces.get()`](https://ably.com/docs/spaces/space#create) - creates a new or retrieves an existing `space`.
+* [`space.member.subscribe()`](https://ably.com/docs/spaces/locking#subscribe) - subscribes to members' online status and profile updates by registering a listener.
+* [`space.enter()`](https://ably.com/docs/spaces/space#enter) - Entering a space will register a client as a member and emit an `enter` event to all subscribers.
+* [`space.locks.acquire()`](https://ably.com/docs/spaces/locking#acquire): a method to attempt to acquire a lock with a given unique ID.
+
+This example also uses [`channels.get()`](https://ably.com/docs/channels#create), [`channel.subscribe()`](https://ably.com/docs/channels#subscribe), and [`channel.publish()`](https://ably.com/docs/channels#publish) from the core Pub/Sub product in order to track component values across multiple clients.
+
+Find out more about [component locking](https://ably.com/docs/spaces/locking).
+
+// End Javascript brief
+
+## View on Github
+
+// React
+
+1. Clone the [Ably docs](https://github.com/ably/docs) repository where this example can be found:
+
+```sh
+git clone git@github.com:ably/docs.git
+```
+
+2. Change directory:
+
+```sh
+cd /examples/spaces-component-locking/react/
+```
+
+3. Rename the environment file:
+
+```sh
+mv .env.example .env.local
+```
+
+4. In `.env.local` update the value of `NEXT_PUBLIC_ABLY_KEY` to be your Ably API key.
+
+5. Install dependencies:
+
+```sh
+yarn install
+```
+
+6. Run the server:
+
+```sh
+yarn run dev
+```
+
+7. Try it out by opening two tabs to [http://localhost:3000/](http://localhost:3000/) with your browser to see the result.
+
+// Javascript
+
+1. Clone the [Ably docs](https://github.com/ably/docs) repository where this example can be found:
+
+```sh
+git clone git@github.com:ably/docs.git
+```
+
+2. Change directory:
+
+```sh
+cd /examples/spaces-component-locking/javascript/
+```
+
+3. Rename the environment file:
+
+```sh
+mv .env.example .env.local
+```
+
+4. In `.env.local` update the value of `VITE_PUBLIC_API_KEY` to be your Ably API key.
+
+5. Install depdencies:
+
+```sh
+yarn install
+```
+
+6. Run the server:
+
+```sh
+yarn run dev
+```
+
+7. Try it out by opening two tabs to [http://localhost:5173/](http://localhost:5173/) with your browser to see the result.
+
+## Open in CodeSandbox
+
+// React
+
+In CodeSandbox, rename the `.env.example` file to `.env.local` and enter the value of your `NEXT_PUBLIC_ABLY_KEY` variable to use your Ably API key.
+
+// Javascript
+
+In CodeSandbox, rename the `.env.example` file to `.env.local` and enter the value of your `VITE_PUBLIC_ABLY_KEY` variable to use your Ably API key.
diff --git a/examples/spaces-component-locking/react/.env.example b/examples/spaces-component-locking/react/.env.example
new file mode 100644
index 0000000000..711655dfe8
--- /dev/null
+++ b/examples/spaces-component-locking/react/.env.example
@@ -0,0 +1 @@
+NEXT_PUBLIC_ABLY_KEY=
\ No newline at end of file
diff --git a/examples/spaces-component-locking/react/.eslintrc.json b/examples/spaces-component-locking/react/.eslintrc.json
new file mode 100644
index 0000000000..bffb357a71
--- /dev/null
+++ b/examples/spaces-component-locking/react/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/examples/spaces-component-locking/react/.gitignore b/examples/spaces-component-locking/react/.gitignore
new file mode 100644
index 0000000000..fd3dbb571a
--- /dev/null
+++ b/examples/spaces-component-locking/react/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/examples/spaces-component-locking/react/README.md b/examples/spaces-component-locking/react/README.md
new file mode 100644
index 0000000000..c06f628e4c
--- /dev/null
+++ b/examples/spaces-component-locking/react/README.md
@@ -0,0 +1,40 @@
+# Locking components within collaborative applications
+
+This folder contains the code for the component locking (React) - a demo of how you can leverage [Ably Spaces](https://github.com/ably/spaces) to lock components within a form or web page.
+
+## Getting started
+
+
+1. Clone the [Ably docs](https://github.com/ably/docs) repository where this example can be found:
+
+```sh
+git clone git@github.com:ably/docs.git
+```
+
+2. Change directory:
+
+```sh
+cd /examples/spaces-component-locking/react/
+```
+
+3. Rename the environment file:
+
+```sh
+mv .env.example .env.local
+```
+
+4. In `.env.local` update the value of `NEXT_PUBLIC_ABLY_KEY` to be your Ably API key.
+
+5. Install dependencies:
+
+```sh
+yarn install
+```
+
+6. Run the server:
+
+```sh
+yarn run dev
+```
+
+7. Try it out by opening two tabs to [http://localhost:3000/](http://localhost:3000/) with your browser to see the result.
diff --git a/examples/spaces-component-locking/react/next.config.mjs b/examples/spaces-component-locking/react/next.config.mjs
new file mode 100644
index 0000000000..4678774e6d
--- /dev/null
+++ b/examples/spaces-component-locking/react/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/examples/spaces-component-locking/react/package.json b/examples/spaces-component-locking/react/package.json
new file mode 100644
index 0000000000..cd8921627c
--- /dev/null
+++ b/examples/spaces-component-locking/react/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "react-component-locking",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@ably/spaces": "^0.4.0",
+ "ably": "^2.3.1",
+ "next": "14.2.5",
+ "react": "^18",
+ "react-dom": "^18",
+ "usehooks-ts": "^3.1.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "eslint": "^8",
+ "eslint-config-next": "14.2.5",
+ "typescript": "^5",
+ "@faker-js/faker": "^9.2.0"
+ }
+}
diff --git a/examples/spaces-component-locking/react/src/app/layout.tsx b/examples/spaces-component-locking/react/src/app/layout.tsx
new file mode 100644
index 0000000000..dd972292f9
--- /dev/null
+++ b/examples/spaces-component-locking/react/src/app/layout.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import { Inter } from "next/font/google";
+import Spaces from "@ably/spaces";
+import { AblyProvider } from "ably/react";
+import { SpaceProvider, SpacesProvider } from "@ably/spaces/react";
+import { Realtime } from 'ably';
+import "../../styles/styles.css";
+
+const inter = Inter({ subsets: ["latin"] });
+
+const client = new Realtime({key: process.env.NEXT_PUBLIC_ABLY_KEY, clientId: "clientId"});
+const spaces = new Spaces(client);
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/examples/spaces-component-locking/react/src/app/page.tsx b/examples/spaces-component-locking/react/src/app/page.tsx
new file mode 100644
index 0000000000..2c5757f524
--- /dev/null
+++ b/examples/spaces-component-locking/react/src/app/page.tsx
@@ -0,0 +1,57 @@
+"use client";
+
+import { useMemo, useEffect } from "react";
+import { useSpace } from "@ably/spaces/react";
+import type { SpaceMember } from "@ably/spaces";
+import { AblyPoweredInput } from "@/components/AblyPoweredInput";
+import { ChannelProvider } from "ably/react";
+import { faker } from '@faker-js/faker';
+import '../../styles/styles.css';
+
+export type Member = Omit & {
+ profileData: { memberColor: string; memberName: string };
+};
+
+const entries = [
+ { label: "Entry 1", name: "entry1" },
+ { label: "Entry 2", name: "entry2" },
+ { label: "Entry 3", name: "entry3" },
+];
+
+export default function Home() {
+ /** 💡 Get a handle on a space instance 💡 */
+ const { space } = useSpace();
+
+ /** 💡 Enter the space as soon as it's available 💡 */
+ useEffect(() => {
+ space?.enter({
+ memberName: faker.person.fullName(),
+ memberColor: faker.color.rgb({ format: 'hex', casing: 'lower' }),
+ });
+ }, [space]);
+
+ return (
+