diff --git a/README.md b/README.md index 0f3772c..667e671 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,172 @@ -# socket.io-redis +# Socket.IO Redis adapter -[![Build Status](https://github.com/socketio/socket.io-redis/workflows/CI/badge.svg?branch=main)](https://github.com/socketio/socket.io-redis/actions) -[![npm version](https://badge.fury.io/js/%40socket.io%2Fredis-adapter.svg)](https://badge.fury.io/js/%40socket.io%2Fredis-adapter) +The `@socket.io/redis-adapter` package allows broadcasting packets between multiple Socket.IO servers. -## Table of contents + + + Diagram of Socket.IO packets forwarded through Redis + -- [How to use](#how-to-use) - - [CommonJS](#commonjs) - - [ES6 module](#es6-modules) - - [TypeScript](#typescript) - - [Sharded Redis Pub/Sub](#sharded-redis-pubsub) +**Table of contents** + +- [Supported features](#supported-features) +- [Installation](#installation) - [Compatibility table](#compatibility-table) -- [How does it work under the hood?](#how-does-it-work-under-the-hood) -- [API](#api) - - [adapter(pubClient, subClient[, opts])](#adapterpubclient-subclient-opts) - - [RedisAdapter](#redisadapter) - - [RedisAdapter#allRooms()](#redisadapterallrooms) -- [With ioredis client](#with-ioredis-client) - - [Cluster example](#cluster-example) - - [Sentinel Example](#sentinel-example) -- [Protocol](#protocol) -- [Migrating from `socket.io-redis`](#migrating-from-socketio-redis) +- [Usage](#usage) + - [With the `redis` package](#with-the-redis-package) + - [With the `redis` package and a Redis cluster](#with-the-redis-package-and-a-redis-cluster) + - [With the `ioredis` package](#with-the-ioredis-package) + - [With the `ioredis` package and a Redis cluster](#with-the-ioredis-package-and-a-redis-cluster) + - [With Redis sharded Pub/Sub](#with-redis-sharded-pubsub) +- [Options](#options) + - [Default adapter](#default-adapter) + - [Sharded adapter](#sharded-adapter) - [License](#license) -## How to use +## Supported features + +| Feature | `socket.io` version | Support | +|---------------------------------|---------------------|------------------------------------------------| +| Socket management | `4.0.0` | :white_check_mark: YES (since version `6.1.0`) | +| Inter-server communication | `4.1.0` | :white_check_mark: YES (since version `7.0.0`) | +| Broadcast with acknowledgements | `4.5.0` | :white_check_mark: YES (since version `7.2.0`) | +| Connection state recovery | `4.6.0` | :x: NO | -Installation: +## Installation ``` -$ npm install @socket.io/redis-adapter redis +npm install @socket.io/redis-adapter ``` -### CommonJS - -```js -const { Server } = require('socket.io'); -const { createClient } = require('redis'); -const { createAdapter } = require('@socket.io/redis-adapter'); +## Compatibility table -const io = new Server(); -const pubClient = createClient({ host: 'localhost', port: 6379 }); -const subClient = pubClient.duplicate(); +| Redis Adapter version | Socket.IO server version | +|-----------------------|--------------------------| +| 4.x | 1.x | +| 5.x | 2.x | +| 6.0.x | 3.x | +| 6.1.x | 4.x | +| 7.x and above | 4.3.1 and above | -Promise.all([pubClient.connect(), subClient.connect()]).then(() => { - io.adapter(createAdapter(pubClient, subClient)); - io.listen(3000); -}); -``` +## Usage -With `redis@3`, calling `connect()` on the Redis clients is not needed: +### With the `redis` package ```js -const { Server } = require('socket.io'); -const { createClient } = require('redis'); -const { createAdapter } = require('@socket.io/redis-adapter'); +import { createClient } from "redis"; +import { Server } from "socket.io"; +import { createAdapter } from "@socket.io/redis-adapter"; -const io = new Server(); -const pubClient = createClient({ host: 'localhost', port: 6379 }); +const pubClient = createClient({ url: "redis://localhost:6379" }); const subClient = pubClient.duplicate(); -io.adapter(createAdapter(pubClient, subClient)); +await Promise.all([ + pubClient.connect(), + subClient.connect() +]); + +const io = new Server({ + adapter: createAdapter(pubClient, subClient) +}); io.listen(3000); ``` -### ES6 modules +### With the `redis` package and a Redis cluster ```js -import { Server } from 'socket.io'; -import { createClient } from 'redis'; -import { createAdapter } from '@socket.io/redis-adapter'; - -const io = new Server(); -const pubClient = createClient({ host: 'localhost', port: 6379 }); +import { createCluster } from "redis"; +import { Server } from "socket.io"; +import { createAdapter } from "@socket.io/redis-adapter"; + +const pubClient = createCluster({ + rootNodes: [ + { + url: "redis://localhost:7000", + }, + { + url: "redis://localhost:7001", + }, + { + url: "redis://localhost:7002", + }, + ], +}); const subClient = pubClient.duplicate(); -Promise.all([pubClient.connect(), subClient.connect()]).then(() => { - io.adapter(createAdapter(pubClient, subClient)); - io.listen(3000); +await Promise.all([ + pubClient.connect(), + subClient.connect() +]); + +const io = new Server({ + adapter: createAdapter(pubClient, subClient) }); + +io.listen(3000); ``` -### TypeScript +### With the `ioredis` package -```ts -import { Server } from 'socket.io'; -import { createClient } from 'redis'; -import { createAdapter } from '@socket.io/redis-adapter'; +```js +import { Redis } from "ioredis"; +import { Server } from "socket.io"; +import { createAdapter } from "@socket.io/redis-adapter"; -const io = new Server(); -const pubClient = createClient({ host: 'localhost', port: 6379 }); +const pubClient = new Redis(); const subClient = pubClient.duplicate(); -Promise.all([pubClient.connect(), subClient.connect()]).then(() => { - io.adapter(createAdapter(pubClient, subClient)); - io.listen(3000); +const io = new Server({ + adapter: createAdapter(pubClient, subClient) }); -``` -By running Socket.IO with the `@socket.io/redis-adapter` adapter you can run -multiple Socket.IO instances in different processes or servers that can -all broadcast and emit events to and from each other. +io.listen(3000); +``` -So any of the following commands: +### With the `ioredis` package and a Redis cluster ```js -io.emit('hello', 'to all clients'); -io.to('room42').emit('hello', "to all clients in 'room42' room"); +import { Cluster } from "ioredis"; +import { Server } from "socket.io"; +import { createAdapter } from "@socket.io/redis-adapter"; -io.on('connection', (socket) => { - socket.broadcast.emit('hello', 'to all clients except sender'); - socket.to('room42').emit('hello', "to all clients in 'room42' room except sender"); -}); -``` +const pubClient = new Cluster([ + { + host: "localhost", + port: 7000, + }, + { + host: "localhost", + port: 7001, + }, + { + host: "localhost", + port: 7002, + }, +]); +const subClient = pubClient.duplicate(); -will properly be broadcast to the clients through the Redis [Pub/Sub mechanism](https://redis.io/topics/pubsub). +const io = new Server({ + adapter: createAdapter(pubClient, subClient) +}); -If you need to emit events to socket.io instances from a non-socket.io -process, you should use [socket.io-emitter](https://github.com/socketio/socket.io-emitter). +io.listen(3000); +``` -### Sharded Redis Pub/Sub +### With Redis sharded Pub/Sub Sharded Pub/Sub was introduced in Redis 7.0 in order to help scaling the usage of Pub/Sub in cluster mode. -Reference: https://redis.io/docs/manual/pubsub/#sharded-pubsub +Reference: https://redis.io/docs/interact/pubsub/#sharded-pubsub A dedicated adapter can be created with the `createShardedAdapter()` method: ```js -import { Server } from 'socket.io'; -import { createClient } from 'redis'; -import { createShardedAdapter } from '@socket.io/redis-adapter'; +import { Server } from "socket.io"; +import { createClient } from "redis"; +import { createShardedAdapter } from "@socket.io/redis-adapter"; -const pubClient = createClient({ host: 'localhost', port: 6379 }); +const pubClient = createClient({ host: "localhost", port: 6379 }); const subClient = pubClient.duplicate(); await Promise.all([ @@ -152,201 +186,26 @@ Minimum requirements: - Redis 7.0 - [`redis@4.6.0`](https://github.com/redis/node-redis/commit/3b1bad229674b421b2bc6424155b20d4d3e45bd1) +Note: it is not currently possible to use the sharded adapter with the `ioredis` package and a Redis cluster ([reference](https://github.com/luin/ioredis/issues/1759)). -## Compatibility table - -| Redis Adapter version | Socket.IO server version | -|-----------------------| ------------------------ | -| 4.x | 1.x | -| 5.x | 2.x | -| 6.0.x | 3.x | -| 6.1.x | 4.x | -| 7.x and above | 4.3.1 and above | - -## How does it work under the hood? - -This adapter extends the [in-memory adapter](https://github.com/socketio/socket.io-adapter/) that is included by default with the Socket.IO server. - -The in-memory adapter stores the relationships between Sockets and Rooms in two Maps. - -When you run `socket.join("room21")`, here's what happens: - -``` -console.log(adapter.rooms); // Map { "room21" => Set { "mdpk4kxF5CmhwfCdAHD8" } } -console.log(adapter.sids); // Map { "mdpk4kxF5CmhwfCdAHD8" => Set { "mdpk4kxF5CmhwfCdAHD8", "room21" } } -// "mdpk4kxF5CmhwfCdAHD8" being the ID of the given socket -``` - -Those two Maps are then used when broadcasting: - -- a broadcast to all sockets (`io.emit()`) loops through the `sids` Map, and send the packet to all sockets -- a broadcast to a given room (`io.to("room21").emit()`) loops through the Set in the `rooms` Map, and sends the packet to all matching sockets - -The Redis adapter extends the broadcast function of the in-memory adapter: the packet is also [published](https://redis.io/topics/pubsub) to a Redis channel (see [below](#protocol) for the format of the channel name). - -Each Socket.IO server receives this packet and broadcasts it to its own list of connected sockets. - -To check what's happening on your Redis instance: - -``` -$ redis-cli -127.0.0.1:6379> PSUBSCRIBE * -Reading messages... (press Ctrl-C to quit) -1) "psubscribe" -2) "*" -3) (integer) 1 - -1) "pmessage" -2) "*" -3) "socket.io#/#" (a broadcast to all sockets or to a list of rooms) -4) - -1) "pmessage" -2) "*" -3) "socket.io#/#room21#" (a broadcast to a single room) -4) -``` - -Note: **no data** is stored in Redis itself - -There are 3 Redis subscriptions per namespace: - -- main channel: `##*` (glob) -- request channel: `-request##` -- response channel: `-response##` - -The request and response channels are used in the additional methods exposed by the Redis adapter, like [RedisAdapter#allRooms()](#redisadapterallrooms). - - -## API - -### adapter(pubClient, subClient[, opts]) - -The following options are allowed: - -- `key`: the name of the key to pub/sub events on as prefix (`socket.io`) -- `requestsTimeout`: optional, after this timeout the adapter will stop waiting from responses to request (`5000ms`) -- `parser`: optional, parser to use for encoding and decoding messages passed through Redis ([`notepack.io`](https://www.npmjs.com/package/notepack.io)) - -### RedisAdapter - -The redis adapter instances expose the following properties -that a regular `Adapter` does not - -- `uid` -- `prefix` -- `pubClient` -- `subClient` -- `requestsTimeout` -- `parser` - -### RedisAdapter#allRooms() - -Returns the list of all rooms. - -```js -const rooms = await io.of('/').adapter.allRooms(); -console.log(rooms); // a Set containing all rooms (across every node) -``` - -## With ioredis client - -### Cluster example - -```js -const io = require('socket.io')(3000); -const redisAdapter = require('@socket.io/redis-adapter'); -const Redis = require('ioredis'); - -const startupNodes = [ - { - port: 6380, - host: '127.0.0.1' - }, - { - port: 6381, - host: '127.0.0.1' - } -]; - -const pubClient = new Redis.Cluster(startupNodes); -const subClient = pubClient.duplicate(); - -io.adapter(redisAdapter(pubClient, subClient)); -``` - -### Sentinel Example - -```js -const io = require('socket.io')(3000); -const redisAdapter = require('@socket.io/redis-adapter'); -const Redis = require('ioredis'); - -const options = { - sentinels: [ - { host: 'somehost1', port: 26379 }, - { host: 'somehost2', port: 26379 } - ], - name: 'master01' -}; - -const pubClient = new Redis(options); -const subClient = pubClient.duplicate(); - -io.adapter(redisAdapter(pubClient, subClient)); -``` - -## Protocol - -The `@socket.io/redis-adapter` adapter broadcasts and receives messages on particularly named Redis channels. For global broadcasts the channel name is: -``` -prefix + '#' + namespace + '#' -``` - -In broadcasting to a single room the channel name is: -``` -prefix + '#' + namespace + '#' + room + '#' -``` - - -- `prefix`: The base channel name. Default value is `socket.io`. Changed by setting `opts.key` in `adapter(opts)` constructor -- `namespace`: See https://github.com/socketio/socket.io#namespace. -- `room` : Used if targeting a specific room. - -A number of other libraries adopt this protocol including: - -- [socket.io-redis-emitter](https://github.com/socketio/socket.io-redis-emitter) -- [socket.io-python-emitter](https://github.com/GameXG/socket.io-python-emitter) -- [socket.io-emitter-go](https://github.com/stackcats/socket.io-emitter-go) - -## Migrating from `socket.io-redis` +## Options -The package was renamed from `socket.io-redis` to `@socket.io/redis-adapter` in [v7](https://github.com/socketio/socket.io-redis-adapter/releases/tag/7.0.0), in order to match the name of the Redis emitter (`@socket.io/redis-emitter`). +### Default adapter -To migrate to the new package, you'll need to make sure to provide your own Redis clients, as the package will no longer create Redis clients on behalf of the user. +| Name | Description | Default value | +|------------------------------------|-------------------------------------------------------------------------------|---------------| +| `key` | The prefix for the Redis Pub/Sub channels. | `socket.io` | +| `requestsTimeout` | After this timeout the adapter will stop waiting from responses to request. | `5_000` | +| `publishOnSpecificResponseChannel` | Whether to publish a response to the channel specific to the requesting node. | `false` | +| `parser` | The parser to use for encoding and decoding messages sent to Redis. | `-` | -Before: - -```js -const redisAdapter = require("socket.io-redis"); - -io.adapter(redisAdapter({ host: "localhost", port: 6379 })); -``` - -After: - -```js -const { createClient } = require("redis"); -const { createAdapter } = require("@socket.io/redis-adapter"); - -const pubClient = createClient({ host: "localhost", port: 6379 }); -const subClient = pubClient.duplicate(); - -io.adapter(createAdapter(pubClient, subClient)); -``` +### Sharded adapter -Please note that the communication protocol between the Socket.IO servers has not been updated, so you can have some servers with `socket.io-redis` and some others with `@socket.io/redis-adapter` at the same time. +| Name | Description | Default value | +|--------------------|-----------------------------------------------------------------------------------------|---------------| +| `channelPrefix` | The prefix for the Redis Pub/Sub channels. | `socket.io` | +| `subscriptionMode` | The subscription mode impacts the number of Redis Pub/Sub channels used by the adapter. | `dynamic` | ## License -MIT +[MIT](LICENSE) diff --git a/assets/adapter.excalidraw b/assets/adapter.excalidraw new file mode 100644 index 0000000..42c349c --- /dev/null +++ b/assets/adapter.excalidraw @@ -0,0 +1,1544 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "version": 184, + "versionNonce": 1779359888, + "isDeleted": false, + "id": "5hUB5ALUlsn26W0PzU4fM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 531, + "y": -120.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 63.95000076293945, + "height": 25, + "seed": 28708370, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "socket", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "socket", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 199, + "versionNonce": 1125439632, + "isDeleted": false, + "id": "lmQ4o4New7xuXQLwavuSn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 461, + "y": -204, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 277, + "height": 311, + "seed": 1594950354, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "_wBO22vaQplcoKyBXbWRC" + }, + { + "type": "arrow", + "id": "BZVwnsrGk9G-X87ZHkh-6" + } + ], + "updated": 1710339987249, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 112, + "versionNonce": 725168240, + "isDeleted": false, + "id": "ZQsZmj4NaTubBHMkVG2dl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 480, + "y": -193, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 84.81666564941406, + "height": 26, + "seed": 126533902, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Server A", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Server A", + "lineHeight": 1.3 + }, + { + "type": "arrow", + "version": 166, + "versionNonce": 1893470352, + "isDeleted": false, + "id": "ABQydsvmkN5ptLyYQaUA3", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 474.8983868047594, + "y": -102.13129275811838, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 251.33111393617446, + "height": 0.7613046474941143, + "seed": 1466702734, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -251.33111393617446, + -0.7613046474941143 + ] + ] + }, + { + "type": "rectangle", + "version": 241, + "versionNonce": 975928976, + "isDeleted": false, + "id": "x54ljUV2PW8AfubZ6fiVJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 73, + "y": -132, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 486293390, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 182, + "versionNonce": 1130179728, + "isDeleted": false, + "id": "zdzgdf3hgOYX0SgjEtyIZ", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 474.24341762810946, + "y": -36.807185424128534, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 247.23231148719788, + "height": 2.2114410393964476, + "seed": 1674715794, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -247.23231148719788, + 2.2114410393964476 + ] + ] + }, + { + "type": "text", + "version": 216, + "versionNonce": 612650640, + "isDeleted": false, + "id": "dXknKeuYe3X3K-0Hw9P95", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 121, + "y": -115, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 40.983333587646484, + "height": 20, + "seed": 1858283854, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "client", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "client", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 278, + "versionNonce": 1162299536, + "isDeleted": false, + "id": "Ce1Lw4MMOtiunstd3FPJv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 74.5, + "y": -57, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 568384654, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 282, + "versionNonce": 261726832, + "isDeleted": false, + "id": "rcCUGk-XM0jKzcGaeO0iS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 121.5, + "y": -40, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 40.983333587646484, + "height": 20, + "seed": 244546386, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "client", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "client", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 284, + "versionNonce": 1884972176, + "isDeleted": false, + "id": "4iido5zQ7QhoIfnOzWp3h", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 74.5, + "y": 18, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 1055485070, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 276, + "versionNonce": 169606288, + "isDeleted": false, + "id": "D1E2DkimaDb8hGxIfXKmq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 121.5, + "y": 35, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 40.983333587646484, + "height": 20, + "seed": 270265170, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "client", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "client", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 237, + "versionNonce": 1314291856, + "isDeleted": false, + "id": "RRrk3Vsl-pM8Z1r8Fj3Vu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 497.5, + "y": -132, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 1013161166, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 244, + "versionNonce": 857461872, + "isDeleted": false, + "id": "8pCtm42TpakWdZ7WNS4VN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 530, + "y": -50.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 63.95000076293945, + "height": 25, + "seed": 684338382, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "socket", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "socket", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 295, + "versionNonce": 2029755536, + "isDeleted": false, + "id": "thsI1AfZ_VshmC8wdQoT_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 496.5, + "y": -62, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 1104563986, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 243, + "versionNonce": 2089675408, + "isDeleted": false, + "id": "dfFxeVTIg6OH8ny7WuBsb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 527, + "y": 26.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 63.95000076293945, + "height": 25, + "seed": 1000469902, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "socket", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "socket", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 296, + "versionNonce": 436844688, + "isDeleted": false, + "id": "Ejm4QTgpRy-0064kg5DDC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 493.5, + "y": 15, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 1070363218, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 217, + "versionNonce": 1063528080, + "isDeleted": false, + "id": "yn0_EJ_FjGmr2PHYTCPsC", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 474.61615574359894, + "y": 42.89427948030175, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 247.23231148719788, + "height": 2.2114410393964476, + "seed": 1559186084, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -247.23231148719788, + 2.2114410393964476 + ] + ] + }, + { + "type": "text", + "version": 193, + "versionNonce": 1267189360, + "isDeleted": false, + "id": "2KQuRzgUL-iSoMHZQ9zbS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 529.5, + "y": 282, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 63.95000076293945, + "height": 25, + "seed": 1479277478, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "socket", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "socket", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 223, + "versionNonce": 324518544, + "isDeleted": false, + "id": "dJhDWOnAJOszWt_UNEXdt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 459.5, + "y": 198.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 277, + "height": 280, + "seed": 224360890, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "qmYaJfZ9NO1RK7YHGQGo6" + }, + { + "type": "arrow", + "id": "x_nMpLlFEV43XGOAM6Gxj" + } + ], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 130, + "versionNonce": 1720850576, + "isDeleted": false, + "id": "lyh4RgaTTCZNLUjl519k9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 478.5, + "y": 209.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86.23332977294922, + "height": 26, + "seed": 364484326, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Server B", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Server B", + "lineHeight": 1.3 + }, + { + "type": "arrow", + "version": 204, + "versionNonce": 1532623504, + "isDeleted": false, + "id": "x7ujWlTTvv0aN7XIFTWjr", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 479.3983868047594, + "y": 301.3687072418816, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 251.33111393617446, + "height": 0.7613046474941143, + "seed": 1836855930, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -251.33111393617446, + -0.7613046474941143 + ] + ] + }, + { + "type": "rectangle", + "version": 211, + "versionNonce": 828123280, + "isDeleted": false, + "id": "cqdTPTcZefvtqeNEAMTBe", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 77.5, + "y": 271.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 1567738406, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 220, + "versionNonce": 585668240, + "isDeleted": false, + "id": "59kripFevaDD2Mo2bkYk-", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 478.74341762810946, + "y": 366.69281457587147, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 247.23231148719788, + "height": 2.2114410393964476, + "seed": 1124324154, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -247.23231148719788, + 2.2114410393964476 + ] + ] + }, + { + "type": "text", + "version": 189, + "versionNonce": 363165808, + "isDeleted": false, + "id": "U0x2FIFxg4BZgOIK6sVnW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 125.5, + "y": 288.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 40.983333587646484, + "height": 20, + "seed": 1044485478, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "client", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "client", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 231, + "versionNonce": 1417687696, + "isDeleted": false, + "id": "NU9potS0F6f8sxY5IT0Lt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 79, + "y": 346.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 1884904442, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 210, + "versionNonce": 467699344, + "isDeleted": false, + "id": "IpJJ20xja0yqXQC_netfw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 126, + "y": 363.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 40.983333587646484, + "height": 20, + "seed": 1635121318, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018665, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "client", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "client", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 250, + "versionNonce": 1105251984, + "isDeleted": false, + "id": "scSxnujNYgELyMUDbnTNS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 496, + "y": 270.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 303703418, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 234, + "versionNonce": 1966926448, + "isDeleted": false, + "id": "Lyv2NwV0SfYm5kvp9sJEn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 528.5, + "y": 352, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 63.95000076293945, + "height": 25, + "seed": 1344309030, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710340018666, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "socket", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "socket", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 291, + "versionNonce": 1288333968, + "isDeleted": false, + "id": "e3D2rl_rbVQwQUKshOG8E", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 495, + "y": 340.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 56, + "seed": 627795514, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "diamond", + "version": 272, + "versionNonce": 2067699856, + "isDeleted": false, + "id": "k0pJTVL4F3HHsfRPlE-gO", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 786, + "y": -58, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 46, + "height": 46, + "seed": 1260350118, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "Sp9AvxDh8gwRvSC53VFKe" + } + ], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 285, + "versionNonce": 1550342288, + "isDeleted": false, + "id": "DiLMkDsU2SrPef3STL9fw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 765.5583343505859, + "y": -97.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 139.88333129882812, + "height": 26, + "seed": 1810644198, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339990684, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Redis adapter", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Redis adapter", + "lineHeight": 1.3 + }, + { + "type": "arrow", + "version": 57, + "versionNonce": 1020103280, + "isDeleted": false, + "id": "Sp9AvxDh8gwRvSC53VFKe", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 766, + "y": -35, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 109, + "height": 1, + "seed": 714162918, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987276, + "link": null, + "locked": false, + "startBinding": { + "elementId": "k0pJTVL4F3HHsfRPlE-gO", + "focus": -0.01715197447147986, + "gap": 14.142135623730947 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -109, + -1 + ] + ] + }, + { + "type": "arrow", + "version": 73, + "versionNonce": 1637006448, + "isDeleted": false, + "id": "_wBO22vaQplcoKyBXbWRC", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 763, + "y": -41, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 105, + "height": 57, + "seed": 1243541542, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987276, + "link": null, + "locked": false, + "startBinding": { + "elementId": "lmQ4o4New7xuXQLwavuSn", + "focus": 0.35224176368590543, + "gap": 25 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -105, + -57 + ] + ] + }, + { + "type": "arrow", + "version": 88, + "versionNonce": 1524739696, + "isDeleted": false, + "id": "BZVwnsrGk9G-X87ZHkh-6", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 765, + "y": -28, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 95, + "height": 62, + "seed": 1890534970, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987276, + "link": null, + "locked": false, + "startBinding": { + "elementId": "lmQ4o4New7xuXQLwavuSn", + "focus": -0.522635330379503, + "gap": 27 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -95, + 62 + ] + ] + }, + { + "type": "diamond", + "version": 378, + "versionNonce": 1094118544, + "isDeleted": false, + "id": "vJwd2LS9grrvUFlbCugEG", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 786.25, + "y": 343, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 46, + "height": 46, + "seed": 1072510330, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "x_nMpLlFEV43XGOAM6Gxj" + } + ], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 277, + "versionNonce": 1904369776, + "isDeleted": false, + "id": "x_nMpLlFEV43XGOAM6Gxj", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 760.25, + "y": 365, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 109, + "height": 1, + "seed": 1180464698, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987277, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dJhDWOnAJOszWt_UNEXdt", + "focus": -0.17704646556482773, + "gap": 23.75 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -109, + -1 + ] + ] + }, + { + "type": "arrow", + "version": 268, + "versionNonce": 356307568, + "isDeleted": false, + "id": "qmYaJfZ9NO1RK7YHGQGo6", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 756.9214748277186, + "y": 355.7229508196721, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 104.67147482771861, + "height": 53.72295081967212, + "seed": 880321126, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987277, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dJhDWOnAJOszWt_UNEXdt", + "focus": 0.304824173970933, + "gap": 20.421474827718612 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -104.67147482771861, + -53.72295081967212 + ] + ] + }, + { + "type": "ellipse", + "version": 135, + "versionNonce": 2074179216, + "isDeleted": false, + "id": "EQmjbilyrf3OcSwGbMZrg", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 841, + "y": 94, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 150.00000000000003, + "height": 93.00000000000001, + "seed": 1885795942, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "xDobZ6graJnZZP8g59wJ4" + }, + { + "type": "arrow", + "id": "eU1gfEXnHSjxc-pEgv43A" + } + ], + "updated": 1710339987250, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 64, + "versionNonce": 857393296, + "isDeleted": false, + "id": "wV6Y3XyIP5TbX50EF6xs6", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 892.0666675567627, + "y": 129.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 51.13333511352539, + "height": 26, + "seed": 1433614630, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1710339993804, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Redis", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "Redis", + "lineHeight": 1.3 + }, + { + "type": "arrow", + "version": 132, + "versionNonce": 2094597232, + "isDeleted": false, + "id": "eU1gfEXnHSjxc-pEgv43A", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 831, + "y": -9, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 47.87517320626739, + "height": 85.84605939285643, + "seed": 1145880934, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339987278, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "EQmjbilyrf3OcSwGbMZrg", + "focus": -0.02048842961361912, + "gap": 22.17434859207502 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 47.87517320626739, + 85.84605939285643 + ] + ] + }, + { + "type": "arrow", + "version": 197, + "versionNonce": 1554695312, + "isDeleted": false, + "id": "xDobZ6graJnZZP8g59wJ4", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 885.1947399534047, + "y": 201.03676246231026, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 29.812198779418623, + "height": 92.55583013028235, + "seed": 1443544058, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1710339997274, + "link": null, + "locked": false, + "startBinding": { + "elementId": "EQmjbilyrf3OcSwGbMZrg", + "focus": 0.14442451001935527, + "gap": 17.685613369250504 + }, + "endBinding": { + "elementId": "g_nwmfFr4gmfrn6naI6-1", + "focus": 0.08950820540708966, + "gap": 14.15740740740739 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -29.812198779418623, + 92.55583013028235 + ] + ] + }, + { + "type": "text", + "version": 339, + "versionNonce": 1211082896, + "isDeleted": false, + "id": "g_nwmfFr4gmfrn6naI6-1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 770.0583343505859, + "y": 307.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 139.88333129882812, + "height": 26, + "seed": 1287799958, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "xDobZ6graJnZZP8g59wJ4" + } + ], + "updated": 1710339997271, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Redis adapter", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Redis adapter", + "lineHeight": 1.3 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/assets/adapter.png b/assets/adapter.png new file mode 100644 index 0000000..6bc0ce5 Binary files /dev/null and b/assets/adapter.png differ diff --git a/assets/adapter_dark.png b/assets/adapter_dark.png new file mode 100644 index 0000000..e8ea796 Binary files /dev/null and b/assets/adapter_dark.png differ