{"version":3,"file":"hocuspocus-redis.cjs","names":["crypto","IncomingMessage","MessageReceiver","RedisClient","Redlock","MessageType","messageYjsSyncStep2","messageYjsUpdate","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\tMessageType,\n\tOutgoingMessage,\n} from \"@hocuspocus/server\";\nimport {\n\tmessageYjsSyncStep2,\n\tmessageYjsUpdate,\n} from \"y-protocols/sync\";\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\t/**\n\t * The maximum time (in ms) `afterLoadDocument` waits for another instance to\n\t * send its state when loading a document that is already open elsewhere.\n\t *\n\t * When a document is loaded on this instance while another instance already\n\t * has it open (and possibly modified in memory, not yet persisted), we\n\t * publish a SyncStep1 and block the load until the peer replies with its\n\t * state (SyncStep2) — or this timeout elapses. This guarantees that callers\n\t * (most importantly a `DirectConnection`) observe the latest collaborative\n\t * state instead of just what was loaded from storage.\n\t *\n\t * If no other instance is subscribed to the document, the wait is skipped\n\t * entirely, so a cold/lone load is not delayed.\n\t *\n\t * Set to `0` to disable the wait and restore the legacy fire-and-forget\n\t * behavior.\n\t */\n\tawaitInitialSyncTimeout: 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\tawaitInitialSyncTimeout: 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\t/**\n\t * Documents this instance has loaded and subscribed to, keyed by name.\n\t *\n\t * This mirrors `instance.documents` but is populated at the very start of\n\t * `afterLoadDocument` — i.e. *before* Hocuspocus registers the document in\n\t * `instance.documents` (which only happens once loading, including this\n\t * hook, has fully resolved). That early registration is what lets\n\t * `handleIncomingMessage` apply a peer's SyncStep2 reply while we are still\n\t * blocking the load on it.\n\t */\n\tprivate documents = new Map<string, Document>();\n\n\t/**\n\t * Resolvers for in-flight `afterLoadDocument` waits, keyed by document name.\n\t * Invoked by `handleIncomingMessage` as soon as a peer's state arrives.\n\t */\n\tprivate pendingInitialSyncResolves = new Map<string, () => void>();\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\t// Track the document locally right away so `handleIncomingMessage` can\n\t\t// apply inbound messages to it even before Hocuspocus has finished\n\t\t// loading it (it only lands in `instance.documents` after this hook\n\t\t// resolves). Without this, the SyncStep2 reply we request below would be\n\t\t// dropped while we wait for it.\n\t\tthis.documents.set(documentName, document);\n\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t// On document creation the node will connect to pub and sub channels\n\t\t\t\t// for the document.\n\t\t\t\tthis.sub.subscribe(this.subKey(documentName), (error: any) => {\n\t\t\t\t\tif (error) {\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tawait this.syncInitialStateFromPeers(documentName, document);\n\t\t} catch (error) {\n\t\t\t// Loading failed: stop tracking the document so future messages aren't\n\t\t\t// applied to a doc that never finished loading, release any pending\n\t\t\t// wait, and best-effort unsubscribe (afterUnloadDocument does not run\n\t\t\t// for a load that never completed).\n\t\t\tthis.documents.delete(documentName);\n\t\t\tthis.pendingInitialSyncResolves.delete(documentName);\n\t\t\tthis.sub.unsubscribe(this.subKey(documentName), () => {});\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Announce our state to other instances and — when another instance already\n\t * has this document open — block until it has sent us its state (or the\n\t * configured timeout elapses). See {@link Configuration.awaitInitialSyncTimeout}.\n\t */\n\tprivate async syncInitialStateFromPeers(\n\t\tdocumentName: string,\n\t\tdocument: Document,\n\t) {\n\t\tconst timeout = this.configuration.awaitInitialSyncTimeout;\n\n\t\tconst waitForPeers =\n\t\t\ttimeout > 0 && (await this.hasOtherSubscribers(documentName));\n\n\t\t// Announce our state and request awareness. Awaited so a Redis publish\n\t\t// failure surfaces as a load error instead of an unhandled rejection.\n\t\tawait Promise.all([\n\t\t\tthis.publishFirstSyncStep(documentName, document),\n\t\t\tthis.requestAwarenessFromOtherInstances(documentName),\n\t\t]);\n\n\t\t// Skip the wait when it's disabled or nobody else has the document open:\n\t\t// there is no peer to sync from, so blocking would only add latency.\n\t\tif (!waitForPeers) {\n\t\t\treturn;\n\t\t}\n\n\t\t// A peer has the document open. Block until it replies with its state\n\t\t// (a SyncStep2/Update applied via handleIncomingMessage resolves this) or\n\t\t// the timeout elapses. The reply cannot arrive before the SyncStep1 we\n\t\t// just published is delivered, so registering the resolver now — right\n\t\t// after the awaited publish, before yielding to the event loop — cannot\n\t\t// miss it.\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pendingInitialSyncResolves.delete(documentName);\n\t\t\t\tresolve();\n\t\t\t}, timeout);\n\n\t\t\tthis.pendingInitialSyncResolves.set(documentName, () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tthis.pendingInitialSyncResolves.delete(documentName);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Whether another instance is currently subscribed to a document's channel.\n\t * We are subscribed ourselves, so a subscriber count greater than one means\n\t * at least one peer has the document open.\n\t */\n\tprivate async hasOtherSubscribers(documentName: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst result = (await this.pub.pubsub(\n\t\t\t\t\"NUMSUB\",\n\t\t\t\tthis.subKey(documentName),\n\t\t\t)) as [string, number | string];\n\t\t\treturn Number(result?.[1] ?? 0) > 1;\n\t\t} catch {\n\t\t\t// NUMSUB may be unavailable in some setups (e.g. certain clusters).\n\t\t\t// Assume peers might exist so correctness is preserved; the timeout\n\t\t\t// still bounds the wait.\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * Resolve a pending `afterLoadDocument` wait once a peer's state has been\n\t * applied.\n\t */\n\tprivate resolveInitialSync(documentName: string) {\n\t\tthis.pendingInitialSyncResolves.get(documentName)?.();\n\t}\n\n\t/**\n\t * Peek whether a message carries another instance's document state\n\t * (SyncStep2 or Update) without consuming the decoder. A SyncStep1 only\n\t * *requests* state, so it must not release an initial-sync wait.\n\t */\n\tprivate messageCarriesPeerState(message: IncomingMessage): boolean {\n\t\tconst { pos } = message.decoder;\n\t\ttry {\n\t\t\tconst type = message.readVarUint();\n\t\t\tif (type !== MessageType.Sync && type !== MessageType.SyncReply) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst syncType = message.readVarUint();\n\t\t\treturn (\n\t\t\t\tsyncType === messageYjsSyncStep2 || syncType === messageYjsUpdate\n\t\t\t);\n\t\t} catch {\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tmessage.decoder.pos = pos;\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\t// Prefer the extension-local map: it also contains documents that are\n\t\t// still loading (not yet in `instance.documents`), which is exactly when\n\t\t// a blocking `afterLoadDocument` needs to receive the peer's reply.\n\t\tconst document =\n\t\t\tthis.documents.get(documentName) ??\n\t\t\tthis.instance.documents.get(documentName);\n\n\t\tif (!document) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst carriesPeerState = this.messageCarriesPeerState(message);\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\n\t\t// A peer answered our SyncStep1 with its state — the document has now\n\t\t// caught up, so any blocking initial-sync wait can be released.\n\t\tif (carriesPeerState) {\n\t\t\tthis.resolveInitialSync(documentName);\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.resolveInitialSync(data.documentName);\n\t\tthis.documents.delete(data.documentName);\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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"}