{"version":3,"file":"hocuspocus-redis.cjs","names":["crypto","IncomingMessage","MessageReceiver","RedisClient","Redlock","OutgoingMessage","ExecutionError","SkipFurtherHooksError"],"sources":["../src/Redis.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport type {\n\tafterLoadDocumentPayload,\n\tafterStoreDocumentPayload,\n\tafterUnloadDocumentPayload,\n\tbeforeBroadcastStatelessPayload,\n\tbeforeUnloadDocumentPayload,\n\tDocument,\n\tExtension,\n\tHocuspocus,\n\tonAwarenessUpdatePayload,\n\tonChangePayload,\n\tonConfigurePayload,\n\tonStoreDocumentPayload,\n\tRedisTransactionOrigin,\n} from \"@hocuspocus/server\";\nimport { SkipFurtherHooksError } from \"@hocuspocus/common\";\nimport {\n\tIncomingMessage,\n\tisTransactionOrigin,\n\tMessageReceiver,\n\tOutgoingMessage,\n} from \"@hocuspocus/server\";\nimport {\n\tExecutionError,\n\ttype ExecutionResult,\n\ttype Lock,\n\tRedlock,\n} from \"@sesamecare-oss/redlock\";\nimport type {\n\tCluster,\n\tClusterNode,\n\tClusterOptions,\n\tRedisOptions,\n} from \"ioredis\";\nimport RedisClient from \"ioredis\";\nexport type RedisInstance = RedisClient | Cluster;\nexport interface Configuration {\n\t/**\n\t * Redis port\n\t */\n\tport: number;\n\t/**\n\t * Redis host\n\t */\n\thost: string;\n\t/**\n\t * Redis Cluster\n\t */\n\tnodes?: ClusterNode[];\n\t/**\n\t * Duplicate from an existed Redis instance\n\t */\n\tredis?: RedisInstance;\n\t/**\n\t * Redis instance creator\n\t */\n\tcreateClient?: () => RedisInstance;\n\t/**\n\t * Options passed directly to Redis constructor\n\t *\n\t * https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options\n\t */\n\toptions?: ClusterOptions | RedisOptions;\n\t/**\n\t * An unique instance name, required to filter messages in Redis.\n\t * If none is provided an unique id is generated.\n\t */\n\tidentifier: string;\n\t/**\n\t * Namespace for Redis keys, if none is provided 'hocuspocus' is used\n\t */\n\tprefix: string;\n\t/**\n\t * The maximum time for the Redis lock in ms (in case it can’t be released).\n\t */\n\tlockTimeout: number;\n\t/**\n\t * A delay before onDisconnect is executed. This allows last minute updates'\n\t * sync messages to be received by the subscription before it's closed.\n\t */\n\tdisconnectDelay: number;\n}\n\nexport class Redis implements Extension {\n\t/**\n\t * Make sure to give that extension a higher priority, so\n\t * the `onStoreDocument` hook is able to intercept the chain,\n\t * before documents are stored to the database.\n\t */\n\tpriority = 1000;\n\n\tconfiguration: Configuration = {\n\t\tport: 6379,\n\t\thost: \"127.0.0.1\",\n\t\tprefix: \"hocuspocus\",\n\t\tidentifier: `host-${crypto.randomUUID()}`,\n\t\tlockTimeout: 1000,\n\t\tdisconnectDelay: 1000,\n\t};\n\n\tredisTransactionOrigin: RedisTransactionOrigin = {\n\t\tsource: \"redis\",\n\t};\n\n\tpub: RedisInstance;\n\n\tsub: RedisInstance;\n\n\tinstance!: Hocuspocus;\n\n\tredlock: Redlock;\n\n\tlocks = new Map<string, { lock: Lock; release?: Promise<ExecutionResult> }>();\n\n\tmessagePrefix: Buffer;\n\n\tprivate pendingAfterStoreDocumentResolves = new Map<\n\t\tstring,\n\t\t{ timeout: NodeJS.Timeout; resolve: () => void }\n\t>();\n\n\tpublic constructor(configuration: Partial<Configuration>) {\n\t\tthis.configuration = {\n\t\t\t...this.configuration,\n\t\t\t...configuration,\n\t\t};\n\n\t\t// Create Redis instance\n\t\tconst { port, host, options, nodes, redis, createClient } =\n\t\t\tthis.configuration;\n\n\t\tif (typeof createClient === \"function\") {\n\t\t\tthis.pub = createClient();\n\t\t\tthis.sub = createClient();\n\t\t} else if (redis) {\n\t\t\tthis.pub = redis.duplicate();\n\t\t\tthis.sub = redis.duplicate();\n\t\t} else if (nodes && nodes.length > 0) {\n\t\t\tthis.pub = new RedisClient.Cluster(nodes, options);\n\t\t\tthis.sub = new RedisClient.Cluster(nodes, options);\n\t\t} else {\n\t\t\tthis.pub = new RedisClient(port, host, options ?? {});\n\t\t\tthis.sub = new RedisClient(port, host, options ?? {});\n\t\t}\n\n\t\tthis.sub.on(\"messageBuffer\", this.handleIncomingMessage);\n\n\t\tthis.redlock = new Redlock([this.pub], {\n\t\t\tretryCount: 0,\n\t\t});\n\n\t\tconst identifierBuffer = Buffer.from(\n\t\t\tthis.configuration.identifier,\n\t\t\t\"utf-8\",\n\t\t);\n\t\tthis.messagePrefix = Buffer.concat([\n\t\t\tBuffer.from([identifierBuffer.length]),\n\t\t\tidentifierBuffer,\n\t\t]);\n\t}\n\n\tasync onConfigure({ instance }: onConfigurePayload) {\n\t\tthis.instance = instance;\n\t}\n\n\tprivate getKey(documentName: string) {\n\t\treturn `${this.configuration.prefix}:${documentName}`;\n\t}\n\n\tprivate pubKey(documentName: string) {\n\t\treturn this.getKey(documentName);\n\t}\n\n\tprivate subKey(documentName: string) {\n\t\treturn this.getKey(documentName);\n\t}\n\n\tprivate lockKey(documentName: string) {\n\t\treturn `${this.getKey(documentName)}:lock`;\n\t}\n\n\tprivate encodeMessage(message: Uint8Array) {\n\t\treturn Buffer.concat([this.messagePrefix, Buffer.from(message)]);\n\t}\n\n\tprivate decodeMessage(buffer: Buffer) {\n\t\tconst identifierLength = buffer[0];\n\t\tconst identifier = buffer.toString(\"utf-8\", 1, identifierLength + 1);\n\n\t\treturn [identifier, buffer.slice(identifierLength + 1)];\n\t}\n\n\t/**\n\t * Once a document is loaded, subscribe to the channel in Redis.\n\t */\n\tpublic async afterLoadDocument({\n\t\tdocumentName,\n\t\tdocument,\n\t}: afterLoadDocumentPayload) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\t// On document creation the node will connect to pub and sub channels\n\t\t\t// for the document.\n\t\t\tthis.sub.subscribe(this.subKey(documentName), async (error: any) => {\n\t\t\t\tif (error) {\n\t\t\t\t\treject(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.publishFirstSyncStep(documentName, document);\n\t\t\t\tthis.requestAwarenessFromOtherInstances(documentName);\n\n\t\t\t\tresolve(undefined);\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Publish the first sync step through Redis.\n\t */\n\tprivate async publishFirstSyncStep(documentName: string, document: Document) {\n\t\tconst syncMessage = new OutgoingMessage(documentName)\n\t\t\t.createSyncMessage()\n\t\t\t.writeFirstSyncStepFor(document);\n\n\t\treturn this.pub.publish(\n\t\t\tthis.pubKey(documentName),\n\t\t\tthis.encodeMessage(syncMessage.toUint8Array()),\n\t\t);\n\t}\n\n\t/**\n\t * Let’s ask Redis who is connected already.\n\t */\n\tprivate async requestAwarenessFromOtherInstances(documentName: string) {\n\t\tconst awarenessMessage = new OutgoingMessage(\n\t\t\tdocumentName,\n\t\t).writeQueryAwareness();\n\n\t\treturn this.pub.publish(\n\t\t\tthis.pubKey(documentName),\n\t\t\tthis.encodeMessage(awarenessMessage.toUint8Array()),\n\t\t);\n\t}\n\n\t/**\n\t * Before the document is stored, make sure to set a lock in Redis.\n\t * That’s meant to avoid conflicts with other instances trying to store the document.\n\t */\n\tasync onStoreDocument({ documentName }: onStoreDocumentPayload) {\n\t\t// Attempt to acquire a lock and read lastReceivedTimestamp from Redis,\n\t\t// to avoid conflict with other instances storing the same document.\n\t\tconst resource = this.lockKey(documentName);\n\t\tconst ttl = this.configuration.lockTimeout;\n\t\ttry {\n\t\t\tconst lock = await this.redlock.acquire([resource], ttl);\n\t\t\tconst oldLock = this.locks.get(resource);\n\t\t\tif (oldLock?.release) {\n\t\t\t\tawait oldLock.release;\n\t\t\t}\n\t\t\tthis.locks.set(resource, { lock });\n\t\t} catch (error: any) {\n\t\t\t//based on: https://github.com/sesamecare/redlock/blob/508e00dcd1e4d2bc6373ce455f4fe847e98a9aab/src/index.ts#L347-L349\n\t\t\tif (\n\t\t\t\terror instanceof ExecutionError &&\n\t\t\t\terror.message ===\n\t\t\t\t\t\"The operation was unable to achieve a quorum during its retry window.\"\n\t\t\t) {\n\t\t\t\t// Expected behavior: Could not acquire lock, another instance locked it already.\n\t\t\t\t// Skip further hooks and retry — the data is safe on the other instance.\n\t\t\t\tthrow new SkipFurtherHooksError(\"Another instance is already storing this document\");\n\t\t\t}\n\t\t\t//unexpected error\n\t\t\tconsole.error(\"unexpected error:\", error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Release the Redis lock, so other instances can store documents.\n\t */\n\tasync afterStoreDocument({\n\t\tdocumentName,\n\t\tlastTransactionOrigin,\n\t}: afterStoreDocumentPayload) {\n\t\tconst lockKey = this.lockKey(documentName);\n\t\tconst lock = this.locks.get(lockKey);\n\t\tif (lock) {\n\t\t\ttry {\n\t\t\t\t// Always try to unlock and clean up the lock\n\t\t\t\tlock.release = lock.lock.release();\n\t\t\t\tawait lock.release;\n\t\t\t} catch {\n\t\t\t\t// Lock will expire on its own after timeout\n\t\t\t} finally {\n\t\t\t\tthis.locks.delete(lockKey);\n\t\t\t}\n\t\t}\n\n\t\t// if the change was initiated by a directConnection (or otherwise local source), we need to delay this hook to make sure sync can finish first.\n\t\t// for provider connections, this usually happens in the onDisconnect hook\n\t\tif (\n\t\t\tisTransactionOrigin(lastTransactionOrigin) &&\n\t\t\tlastTransactionOrigin.source === \"local\"\n\t\t) {\n\t\t\tconst pending = this.pendingAfterStoreDocumentResolves.get(documentName);\n\n\t\t\tif (pending) {\n\t\t\t\tclearTimeout(pending.timeout);\n\t\t\t\tpending.resolve();\n\t\t\t\tthis.pendingAfterStoreDocumentResolves.delete(documentName);\n\t\t\t}\n\n\t\t\tlet resolveFunction: () => void = () => {};\n\t\t\tconst delayedPromise = new Promise<void>((resolve) => {\n\t\t\t\tresolveFunction = resolve;\n\t\t\t});\n\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.pendingAfterStoreDocumentResolves.delete(documentName);\n\t\t\t\tresolveFunction();\n\t\t\t}, this.configuration.disconnectDelay);\n\n\t\t\tthis.pendingAfterStoreDocumentResolves.set(documentName, {\n\t\t\t\ttimeout,\n\t\t\t\tresolve: resolveFunction,\n\t\t\t});\n\n\t\t\tawait delayedPromise;\n\t\t}\n\t}\n\n\t/**\n\t * Handle awareness update messages received directly by this Hocuspocus instance.\n\t */\n\tasync onAwarenessUpdate({\n\t\tdocumentName,\n\t\tawareness,\n\t\tadded,\n\t\tupdated,\n\t\tremoved,\n\t\tdocument,\n\t}: onAwarenessUpdatePayload) {\n\t\t// Do not publish if there is no connection: it fixes this issue: \"https://github.com/ueberdosis/hocuspocus/issues/1027\"\n\t\tconst connections = document?.connections.size || 0;\n\t\tif (connections === 0) {\n\t\t\treturn; // avoids exception\n\t\t}\n\t\tconst changedClients = added.concat(updated, removed);\n\t\tconst message = new OutgoingMessage(\n\t\t\tdocumentName,\n\t\t).createAwarenessUpdateMessage(awareness, changedClients);\n\n\t\treturn this.pub.publish(\n\t\t\tthis.pubKey(documentName),\n\t\t\tthis.encodeMessage(message.toUint8Array()),\n\t\t);\n\t}\n\n\t/**\n\t * Handle incoming messages published on subscribed document channels.\n\t * Note that this will also include messages from ourselves as it is not possible\n\t * in Redis to filter these.\n\t */\n\tprivate handleIncomingMessage = async (channel: Buffer, data: Buffer) => {\n\t\tconst [identifier, messageBuffer] = this.decodeMessage(data);\n\n\t\tif (identifier === this.configuration.identifier) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst message = new IncomingMessage(messageBuffer);\n\t\tconst documentName = message.readVarString();\n\t\tmessage.writeVarString(documentName);\n\n\t\tconst document = this.instance.documents.get(documentName);\n\n\t\tif (!document) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst receiver = new MessageReceiver(message, this.redisTransactionOrigin);\n\t\tawait receiver.apply(document, undefined, (reply) => {\n\t\t\treturn this.pub.publish(\n\t\t\t\tthis.pubKey(document.name),\n\t\t\t\tthis.encodeMessage(reply),\n\t\t\t);\n\t\t});\n\t};\n\n\t/**\n\t * if the ydoc changed, we'll need to inform other Hocuspocus servers about it.\n\t */\n\tpublic async onChange(data: onChangePayload): Promise<any> {\n\t\tif (\n\t\t\tisTransactionOrigin(data.transactionOrigin) &&\n\t\t\tdata.transactionOrigin.source === \"redis\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn this.publishFirstSyncStep(data.documentName, data.document);\n\t}\n\n\t/**\n\t * Delay unloading to allow syncs to finish\n\t */\n\tasync beforeUnloadDocument(data: beforeUnloadDocumentPayload) {\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tsetTimeout(() => {\n\t\t\t\tresolve();\n\t\t\t}, this.configuration.disconnectDelay);\n\t\t});\n\t}\n\n\tasync afterUnloadDocument(data: afterUnloadDocumentPayload) {\n\t\tif (data.instance.documents.has(data.documentName)) return; // skip unsubscribe if the document is already loaded again (maybe fast reconnect)\n\n\t\tthis.sub.unsubscribe(this.subKey(data.documentName), (error: any) => {\n\t\t\tif (error) {\n\t\t\t\tconsole.error(error);\n\t\t\t}\n\t\t});\n\t}\n\n\tasync beforeBroadcastStateless(data: beforeBroadcastStatelessPayload) {\n\t\tconst message = new OutgoingMessage(\n\t\t\tdata.documentName,\n\t\t).writeBroadcastStateless(data.payload);\n\n\t\treturn this.pub.publish(\n\t\t\tthis.pubKey(data.documentName),\n\t\t\tthis.encodeMessage(message.toUint8Array()),\n\t\t);\n\t}\n\n\t/**\n\t * Kill the Redlock connection immediately.\n\t */\n\tasync onDestroy() {\n\t\tawait this.redlock.quit();\n\t\tthis.pub.disconnect(false);\n\t\tthis.sub.disconnect(false);\n\t}\n}\n"],"mappings":"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"}