import { AuthMessageType, ConnectionTimeout, Forbidden, ResetConnection, SkipFurtherHooksError, Unauthorized, WsReadyStates, awarenessStatesToArray, writeAuthenticated, writePermissionDenied, writeTokenSyncRequest } from "@hocuspocus/common";
import * as decoding from "lib0/decoding";
import { createDecoder, readVarString, readVarUint, readVarUint8Array } from "lib0/decoding";
import { createEncoder, length, toUint8Array, writeVarString, writeVarUint, writeVarUint8Array } from "lib0/encoding";
import { Awareness, applyAwarenessUpdate, encodeAwarenessUpdate, removeAwarenessStates } from "y-protocols/awareness";
import { messageYjsSyncStep1, messageYjsSyncStep2, messageYjsUpdate, readSyncStep1, readSyncStep2, readUpdate, writeSyncStep1, writeUpdate } from "y-protocols/sync";
import * as Y from "yjs";
import { Doc, applyUpdate, encodeStateAsUpdate } from "yjs";
import { Mutex } from "async-mutex";
import crypto from "node:crypto";
import { URLSearchParams as URLSearchParams$1 } from "node:url";
import { createServer } from "node:http";
import crossws from "crossws/adapters/node";
import kleur from "kleur";

//#region packages/server/src/IncomingMessage.ts
var IncomingMessage = class {
	constructor(input) {
		if (!(input instanceof Uint8Array)) input = new Uint8Array(input);
		this.decoder = createDecoder(input);
	}
	get encoder() {
		if (!this.encoderInternal) this.encoderInternal = createEncoder();
		return this.encoderInternal;
	}
	readVarUint8Array() {
		return readVarUint8Array(this.decoder);
	}
	peekVarUint8Array() {
		const { pos } = this.decoder;
		const result = readVarUint8Array(this.decoder);
		this.decoder.pos = pos;
		return result;
	}
	readVarUint() {
		return readVarUint(this.decoder);
	}
	readVarString() {
		return readVarString(this.decoder);
	}
	toUint8Array() {
		return toUint8Array(this.encoder);
	}
	writeVarUint(type) {
		writeVarUint(this.encoder, type);
	}
	writeVarString(string) {
		writeVarString(this.encoder, string);
	}
	get length() {
		return length(this.encoder);
	}
};

//#endregion
//#region packages/server/src/types.ts
function isTransactionOrigin(origin) {
	return typeof origin === "object" && origin !== null && "source" in origin && (origin.source === "connection" || origin.source === "redis" || origin.source === "local");
}
function shouldSkipStoreHooks(origin) {
	if (!isTransactionOrigin(origin)) return false;
	switch (origin.source) {
		case "connection": return false;
		case "redis": return true;
		case "local": return origin.skipStoreHooks ?? false;
	}
}
let MessageType = /* @__PURE__ */ function(MessageType) {
	MessageType[MessageType["Unknown"] = -1] = "Unknown";
	MessageType[MessageType["Sync"] = 0] = "Sync";
	MessageType[MessageType["Awareness"] = 1] = "Awareness";
	MessageType[MessageType["Auth"] = 2] = "Auth";
	MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
	MessageType[MessageType["SyncReply"] = 4] = "SyncReply";
	MessageType[MessageType["Stateless"] = 5] = "Stateless";
	MessageType[MessageType["BroadcastStateless"] = 6] = "BroadcastStateless";
	MessageType[MessageType["CLOSE"] = 7] = "CLOSE";
	MessageType[MessageType["SyncStatus"] = 8] = "SyncStatus";
	MessageType[MessageType["Ping"] = 9] = "Ping";
	MessageType[MessageType["Pong"] = 10] = "Pong";
	return MessageType;
}({});

//#endregion
//#region packages/server/src/OutgoingMessage.ts
var OutgoingMessage = class {
	constructor(documentName) {
		this.encoder = createEncoder();
		writeVarString(this.encoder, documentName);
	}
	createSyncMessage() {
		this.type = MessageType.Sync;
		writeVarUint(this.encoder, MessageType.Sync);
		return this;
	}
	createSyncReplyMessage() {
		this.type = MessageType.SyncReply;
		writeVarUint(this.encoder, MessageType.SyncReply);
		return this;
	}
	createAwarenessUpdateMessage(awareness, changedClients) {
		this.type = MessageType.Awareness;
		this.category = "Update";
		const message = encodeAwarenessUpdate(awareness, changedClients || Array.from(awareness.getStates().keys()));
		writeVarUint(this.encoder, MessageType.Awareness);
		writeVarUint8Array(this.encoder, message);
		return this;
	}
	writeQueryAwareness() {
		this.type = MessageType.QueryAwareness;
		this.category = "Update";
		writeVarUint(this.encoder, MessageType.QueryAwareness);
		return this;
	}
	writeTokenSyncRequest() {
		this.type = MessageType.Auth;
		this.category = "TokenSync";
		writeVarUint(this.encoder, MessageType.Auth);
		writeTokenSyncRequest(this.encoder);
		return this;
	}
	writeAuthenticated(readonly) {
		this.type = MessageType.Auth;
		this.category = "Authenticated";
		writeVarUint(this.encoder, MessageType.Auth);
		writeAuthenticated(this.encoder, readonly ? "readonly" : "read-write");
		return this;
	}
	writePermissionDenied(reason) {
		this.type = MessageType.Auth;
		this.category = "PermissionDenied";
		writeVarUint(this.encoder, MessageType.Auth);
		writePermissionDenied(this.encoder, reason);
		return this;
	}
	writeFirstSyncStepFor(document) {
		this.category = "SyncStep1";
		writeSyncStep1(this.encoder, document);
		return this;
	}
	writeUpdate(update) {
		this.category = "Update";
		writeUpdate(this.encoder, update);
		return this;
	}
	writeStateless(payload) {
		this.category = "Stateless";
		writeVarUint(this.encoder, MessageType.Stateless);
		writeVarString(this.encoder, payload);
		return this;
	}
	writeBroadcastStateless(payload) {
		this.category = "Stateless";
		writeVarUint(this.encoder, MessageType.BroadcastStateless);
		writeVarString(this.encoder, payload);
		return this;
	}
	writeSyncStatus(updateSaved) {
		this.category = "SyncStatus";
		writeVarUint(this.encoder, MessageType.SyncStatus);
		writeVarUint(this.encoder, updateSaved ? 1 : 0);
		return this;
	}
	writeCloseMessage(reason) {
		this.type = MessageType.CLOSE;
		writeVarUint(this.encoder, MessageType.CLOSE);
		writeVarString(this.encoder, reason);
		return this;
	}
	toUint8Array() {
		return toUint8Array(this.encoder);
	}
};

//#endregion
//#region packages/server/src/MessageReceiver.ts
var MessageReceiver = class {
	constructor(message, defaultTransactionOrigin) {
		this.message = message;
		this.defaultTransactionOrigin = defaultTransactionOrigin;
	}
	async apply(document, connection, reply) {
		const { message } = this;
		const type = message.readVarUint();
		const emptyMessageLength = message.length;
		switch (type) {
			case MessageType.Sync:
			case MessageType.SyncReply:
				message.writeVarUint(MessageType.Sync);
				await this.readSyncMessage(message, document, connection, reply, type !== MessageType.SyncReply);
				if (message.length > emptyMessageLength + 1) {
					if (reply) reply(message.toUint8Array());
					else if (connection) connection.send(message.toUint8Array());
				}
				break;
			case MessageType.Awareness: {
				let update = message.readVarUint8Array();
				const origin = connection ? {
					source: "connection",
					connection
				} : this.defaultTransactionOrigin ?? { source: "local" };
				const scratch = new Awareness(new Y.Doc());
				applyAwarenessUpdate(scratch, update, null);
				await document.callbacks.beforeHandleAwareness(document, scratch.getStates(), origin);
				update = encodeAwarenessUpdate(scratch, [...scratch.getStates().keys()]);
				applyAwarenessUpdate(document.awareness, update, origin);
				break;
			}
			case MessageType.QueryAwareness:
				this.applyQueryAwarenessMessage(document, connection, reply);
				break;
			case MessageType.Stateless:
				connection?.callbacks.statelessCallback({
					connection,
					documentName: document.name,
					document,
					payload: readVarString(message.decoder)
				});
				break;
			case MessageType.BroadcastStateless: {
				const msg = message.readVarString();
				document.getConnections().forEach((connection) => {
					connection.sendStateless(msg);
				});
				break;
			}
			case MessageType.CLOSE:
				connection?.close({
					code: 1e3,
					reason: "provider_initiated"
				});
				break;
			case MessageType.Auth:
				if (message.readVarUint() === AuthMessageType.Token) {
					connection?.callbacks.onTokenSyncCallback({ token: message.readVarString() });
					break;
				}
				console.error("Received an authentication message on a connection that is already fully authenticated. Probably your provider has been destroyed + recreated really fast.");
				break;
			default: console.error(`Unable to handle message of type ${type}: no handler defined! Are your provider/server versions aligned?`);
		}
	}
	async readSyncMessage(message, document, connection, reply, requestFirstSync = true) {
		const type = message.readVarUint();
		const messageAddress = connection?.messageAddress ?? document.name;
		if (connection) await connection.callbacks.beforeSync(connection, {
			type,
			payload: message.peekVarUint8Array()
		});
		switch (type) {
			case messageYjsSyncStep1:
				readSyncStep1(message.decoder, message.encoder, document);
				if (reply && requestFirstSync) reply(new OutgoingMessage(messageAddress).createSyncReplyMessage().writeFirstSyncStepFor(document).toUint8Array());
				else if (connection) {
					const syncMessage = new OutgoingMessage(messageAddress).createSyncMessage().writeFirstSyncStepFor(document);
					connection.send(syncMessage.toUint8Array());
				}
				break;
			case messageYjsSyncStep2:
				if (connection?.readOnly) {
					const snapshot = Y.snapshot(document);
					const update = decoding.readVarUint8Array(message.decoder);
					if (Y.snapshotContainsUpdate(snapshot, update)) {
						const ackMessage = new OutgoingMessage(messageAddress).writeSyncStatus(true);
						connection.send(ackMessage.toUint8Array());
					} else {
						const ackMessage = new OutgoingMessage(messageAddress).writeSyncStatus(false);
						connection.send(ackMessage.toUint8Array());
					}
					break;
				}
				readSyncStep2(message.decoder, document, connection ? {
					source: "connection",
					connection
				} : this.defaultTransactionOrigin ?? { source: "local" });
				if (connection) connection.send(new OutgoingMessage(messageAddress).writeSyncStatus(true).toUint8Array());
				break;
			case messageYjsUpdate:
				if (connection?.readOnly) {
					connection.send(new OutgoingMessage(messageAddress).writeSyncStatus(false).toUint8Array());
					break;
				}
				readUpdate(message.decoder, document, connection ? {
					source: "connection",
					connection
				} : this.defaultTransactionOrigin ?? { source: "local" });
				if (connection) connection.send(new OutgoingMessage(messageAddress).writeSyncStatus(true).toUint8Array());
				break;
			default: throw new Error(`Received a message with an unknown type: ${type}`);
		}
		return type;
	}
	applyQueryAwarenessMessage(document, connection, reply) {
		const message = new OutgoingMessage(connection?.messageAddress ?? document.name).createAwarenessUpdateMessage(document.awareness);
		if (reply) reply(message.toUint8Array());
	}
};

//#endregion
//#region packages/server/src/Connection.ts
var Connection = class {
	/**
	* The address string prefixed to outgoing messages.
	* Session-aware clients get `documentName\0sessionId`; legacy clients get plain `documentName`.
	*/
	get messageAddress() {
		return this.sessionId ? `${this.document.name}\0${this.sessionId}` : this.document.name;
	}
	/**
	* Constructor.
	*/
	constructor(connection, request, document, socketId, context, readOnly = false, sessionId, providerVersion) {
		this.callbacks = {
			onClose: [(document, event) => {}],
			beforeHandleMessage: (connection, update) => Promise.resolve(),
			beforeSync: (connection, payload) => Promise.resolve(),
			statelessCallback: (payload) => Promise.resolve(),
			onTokenSyncCallback: (payload) => Promise.resolve()
		};
		this.messageQueue = [];
		this.processingPromise = Promise.resolve();
		this.webSocket = connection;
		this.context = context;
		this.document = document;
		this.request = request;
		this.socketId = socketId;
		this.readOnly = readOnly;
		this.sessionId = sessionId ?? null;
		this.providerVersion = providerVersion ?? null;
		this.document.addConnection(this);
		this.sendCurrentAwareness();
	}
	/**
	* Set a callback that will be triggered when the connection is closed
	*/
	onClose(callback) {
		this.callbacks.onClose.push(callback);
		return this;
	}
	/**
	* Set a callback that will be triggered when an stateless message is received
	*/
	onStatelessCallback(callback) {
		this.callbacks.statelessCallback = callback;
		return this;
	}
	/**
	* Set a callback that will be triggered before an message is handled
	*/
	beforeHandleMessage(callback) {
		this.callbacks.beforeHandleMessage = callback;
		return this;
	}
	/**
	* Set a callback that will be triggered before a sync message is handled
	*/
	beforeSync(callback) {
		this.callbacks.beforeSync = callback;
		return this;
	}
	/**
	* Set a callback that will be triggered when on token sync message is received
	*/
	onTokenSyncCallback(callback) {
		this.callbacks.onTokenSyncCallback = callback;
		return this;
	}
	/**
	* Returns a promise that resolves when all queued messages have been processed.
	*/
	waitForPendingMessages() {
		return this.processingPromise;
	}
	/**
	* Send the given message
	*/
	send(message) {
		if (this.webSocket.readyState === WsReadyStates.Closing || this.webSocket.readyState === WsReadyStates.Closed) {
			this.close();
			return;
		}
		try {
			this.webSocket.send(message);
		} catch (exception) {
			this.close();
		}
	}
	/**
	* Send a stateless message with payload
	*/
	sendStateless(payload) {
		const message = new OutgoingMessage(this.messageAddress).writeStateless(payload);
		this.send(message.toUint8Array());
	}
	/**
	* Request current token from the client
	*/
	requestToken() {
		const message = new OutgoingMessage(this.messageAddress).writeTokenSyncRequest();
		this.send(message.toUint8Array());
	}
	/**
	* Graceful wrapper around the WebSocket close method.
	*/
	close(event) {
		if (this.document.hasConnection(this)) {
			this.document.removeConnection(this);
			this.callbacks.onClose.forEach((callback) => callback(this.document, event));
			const closeMessage = new OutgoingMessage(this.messageAddress);
			closeMessage.writeCloseMessage(event?.reason ?? "Server closed the connection");
			this.send(closeMessage.toUint8Array());
		}
	}
	/**
	* Send the current document awareness to the client, if any
	* @private
	*/
	sendCurrentAwareness() {
		if (!this.document.hasAwarenessStates()) return;
		const awarenessMessage = new OutgoingMessage(this.messageAddress).createAwarenessUpdateMessage(this.document.awareness);
		this.send(awarenessMessage.toUint8Array());
	}
	/**
	* Handle an incoming message
	* @public
	*/
	handleMessage(data) {
		this.messageQueue.push(data);
		if (this.messageQueue.length === 1) this.processingPromise = this.processMessages();
	}
	async processMessages() {
		while (this.messageQueue.length > 0) {
			const rawUpdate = this.messageQueue.at(0);
			const message = new IncomingMessage(rawUpdate);
			const rawKey = message.readVarString();
			const sepIdx = rawKey.indexOf("\0");
			const documentName = sepIdx === -1 ? rawKey : rawKey.substring(0, sepIdx);
			if (documentName !== this.document.name) {
				this.messageQueue.shift();
				continue;
			}
			message.writeVarString(this.messageAddress);
			try {
				await this.callbacks.beforeHandleMessage(this, rawUpdate);
				await new MessageReceiver(message).apply(this.document, this);
			} catch (e) {
				console.error(`closing connection ${this.socketId} (while handling ${documentName}) because of exception`, e);
				this.close({
					code: "code" in e && typeof e.code === "number" ? e.code : ResetConnection.code,
					reason: "reason" in e ? e.reason : ResetConnection.reason
				});
			}
			this.messageQueue.shift();
		}
	}
};

//#endregion
//#region packages/server/src/Document.ts
var Document = class extends Doc {
	/**
	* Constructor.
	*/
	constructor(name, yDocOptions) {
		super(yDocOptions);
		this.callbacks = {
			onUpdate: (document, origin, update) => {},
			beforeBroadcastStateless: (document, stateless) => {},
			beforeHandleAwareness: (document, states, transactionOrigin) => Promise.resolve()
		};
		this.connections = /* @__PURE__ */ new Map();
		this.directConnectionsCount = 0;
		this.isDestroyed = false;
		this.saveMutex = new Mutex();
		this.lastChangeTime = 0;
		this.name = name;
		this.awareness = new Awareness(this);
		this.awareness.setLocalState(null);
		this.awareness.on("update", this.handleAwarenessUpdate.bind(this));
		this.on("update", this.handleUpdate.bind(this));
		this.isLoading = true;
	}
	/**
	* Check if the Document (XMLFragment or Map) is empty
	*/
	isEmpty(fieldName) {
		return !this.get(fieldName)._start && !this.get(fieldName)._map.size;
	}
	/**
	* Merge the given document(s) into this one
	*/
	merge(documents) {
		(Array.isArray(documents) ? documents : [documents]).forEach((document) => {
			applyUpdate(this, encodeStateAsUpdate(document));
		});
		return this;
	}
	/**
	* Set a callback that will be triggered when the document is updated
	*/
	onUpdate(callback) {
		this.callbacks.onUpdate = callback;
		return this;
	}
	/**
	* Set a callback that will be triggered before a stateless message is broadcasted
	*/
	beforeBroadcastStateless(callback) {
		this.callbacks.beforeBroadcastStateless = callback;
		return this;
	}
	/**
	* Set a callback that will be triggered before an inbound awareness update
	* is applied to this document's awareness state. The callback receives the
	* document, the decoded per-client states as a mutable `Map`, and the
	* `TransactionOrigin` that will be forwarded to `applyAwarenessUpdate`.
	* Use `isTransactionOrigin(origin)` to discriminate sources. Mutate the
	* map in place (set/delete/field changes) to rewrite the update, or throw
	* to reject it entirely.
	*/
	beforeHandleAwareness(callback) {
		this.callbacks.beforeHandleAwareness = callback;
		return this;
	}
	/**
	* Register a connection and a set of clients on this document keyed by the
	* underlying websocket connection
	*/
	addConnection(connection) {
		this.connections.set(connection, { clients: /* @__PURE__ */ new Set() });
		return this;
	}
	/**
	* Is the given connection registered on this document
	*/
	hasConnection(connection) {
		return this.connections.has(connection);
	}
	/**
	* Remove the given connection from this document
	*/
	removeConnection(connection) {
		const entry = this.connections.get(connection);
		if (entry) removeAwarenessStates(this.awareness, Array.from(entry.clients), null);
		this.connections.delete(connection);
		return this;
	}
	addDirectConnection() {
		this.directConnectionsCount += 1;
		return this;
	}
	removeDirectConnection() {
		if (this.directConnectionsCount > 0) this.directConnectionsCount -= 1;
		return this;
	}
	/**
	* Get the number of active connections for this document
	*/
	getConnectionsCount() {
		return this.connections.size + this.directConnectionsCount;
	}
	/**
	* Get an array of registered connections
	*/
	getConnections() {
		return Array.from(this.connections.keys());
	}
	/**
	* Get the client ids for the given connection instance
	*/
	getClients(connection) {
		const entry = this.connections.get(connection);
		return entry?.clients === void 0 ? /* @__PURE__ */ new Set() : entry.clients;
	}
	/**
	* Has the document awareness states
	*/
	hasAwarenessStates() {
		return this.awareness.getStates().size > 0;
	}
	/**
	* Apply the given awareness update
	*/
	applyAwarenessUpdate(connection, update) {
		applyAwarenessUpdate(this.awareness, update, {
			source: "connection",
			connection
		});
		return this;
	}
	/**
	* Handle an awareness update and sync changes to clients
	* @private
	*/
	handleAwarenessUpdate({ added, updated, removed }, origin) {
		const changedClients = added.concat(updated, removed);
		const originConnection = isTransactionOrigin(origin) && origin.source === "connection" ? origin.connection : null;
		if (originConnection !== null) {
			const entry = this.connections.get(originConnection);
			if (entry) {
				added.forEach((clientId) => entry.clients.add(clientId));
				removed.forEach((clientId) => entry.clients.delete(clientId));
			}
		}
		for (const connection of this.getConnections()) {
			const awarenessMessage = new OutgoingMessage(connection.messageAddress).createAwarenessUpdateMessage(this.awareness, changedClients);
			connection.send(awarenessMessage.toUint8Array());
		}
		return this;
	}
	/**
	* Handle an updated document and sync changes to clients
	*/
	handleUpdate(update, origin) {
		this.callbacks.onUpdate(this, origin, update);
		for (const connection of this.getConnections()) {
			const message = new OutgoingMessage(connection.messageAddress).createSyncMessage().writeUpdate(update);
			connection.send(message.toUint8Array());
		}
		return this;
	}
	/**
	* Broadcast stateless message to all connections
	*/
	broadcastStateless(payload, filter) {
		this.callbacks.beforeBroadcastStateless(this, payload);
		(filter ? this.getConnections().filter(filter) : this.getConnections()).forEach((connection) => {
			connection.sendStateless(payload);
		});
	}
	destroy() {
		super.destroy();
		this.isDestroyed = true;
	}
};

//#endregion
//#region packages/server/package.json
var version = "4.1.0";

//#endregion
//#region packages/server/src/util/getParameters.ts
/**
* Get parameters by the given request
*/
function getParameters(request) {
	const url = request?.url;
	if (!url) return new URLSearchParams();
	return url.includes("://") ? new URL(url).searchParams : new URLSearchParams(url.split("?")[1] || "");
}

//#endregion
//#region packages/server/src/ClientConnection.ts
/**
* The `ClientConnection` class is responsible for handling an incoming WebSocket
*
* TODO-refactor:
* - use event handlers instead of calling hooks directly, hooks should probably be called from Hocuspocus.ts
*/
var ClientConnection = class {
	/**
	* The `ClientConnection` class receives incoming WebSocket connections,
	* runs all hooks:
	*
	*  - onConnect for all connections
	*  - onAuthenticate only if required
	*
	* … and if nothings fails it'll fully establish the connection and
	* load the Document then.
	*/
	constructor(websocket, request, documentProvider, hooks, opts, defaultContext = {}) {
		this.websocket = websocket;
		this.request = request;
		this.documentProvider = documentProvider;
		this.hooks = hooks;
		this.opts = opts;
		this.defaultContext = defaultContext;
		this.documentConnections = {};
		this.incomingMessageQueue = {};
		this.documentConnectionsEstablished = /* @__PURE__ */ new Set();
		this.hookPayloads = {};
		this.callbacks = { onClose: [(document, payload) => {}] };
		this.socketId = crypto.randomUUID();
		this.lastMessageReceivedAt = Date.now();
		this.check = () => {
			if (Date.now() - this.lastMessageReceivedAt > this.timeout) this.close(ConnectionTimeout);
		};
		this.setUpNewConnection = async (rawKey, documentName, sessionId) => {
			const hookPayload = this.hookPayloads[rawKey];
			const document = await this.documentProvider.createDocument(documentName, hookPayload.request, hookPayload.socketId, hookPayload.connectionConfig, hookPayload.context);
			const connection = this.createConnection(this.websocket, document, hookPayload, sessionId, hookPayload.providerVersion);
			connection.onClose((document, event) => {
				delete this.hookPayloads[rawKey];
				delete this.documentConnections[rawKey];
				delete this.incomingMessageQueue[rawKey];
				this.documentConnectionsEstablished.delete(rawKey);
			});
			connection.onTokenSyncCallback(async (payload) => {
				try {
					return await this.hooks("onTokenSync", {
						...hookPayload,
						...payload,
						document,
						connection,
						documentName
					}, (contextAdditions) => {
						hookPayload.context = {
							...hookPayload.context,
							...contextAdditions
						};
					});
				} catch (err) {
					console.error(err);
					const error = {
						...Unauthorized,
						...err
					};
					connection.close({
						code: error.code,
						reason: error.reason
					});
				}
			});
			this.documentConnections[rawKey] = connection;
			if (this.websocket.readyState === WsReadyStates.Closing || this.websocket.readyState === WsReadyStates.Closed) {
				this.close();
				return;
			}
			this.incomingMessageQueue[rawKey]?.forEach((input) => {
				connection.handleMessage(input);
			});
			await this.hooks("connected", {
				...hookPayload,
				documentName,
				context: hookPayload.context,
				connection
			});
		};
		this.handleQueueingMessage = async (data, rawKey, documentName) => {
			try {
				const tmpMsg = new IncomingMessage(data);
				decoding.readVarString(tmpMsg.decoder);
				if (!(decoding.readVarUint(tmpMsg.decoder) === MessageType.Auth && !this.documentConnectionsEstablished.has(rawKey))) {
					this.incomingMessageQueue[rawKey].push(data);
					return;
				}
				this.documentConnectionsEstablished.add(rawKey);
				decoding.readVarUint(tmpMsg.decoder);
				const token = decoding.readVarString(tmpMsg.decoder);
				let providerVersion = null;
				if (decoding.hasContent(tmpMsg.decoder)) providerVersion = decoding.readVarString(tmpMsg.decoder);
				const sepIdx = rawKey.indexOf("\0");
				const sessionId = sepIdx === -1 ? null : rawKey.substring(sepIdx + 1);
				const responseAddress = rawKey;
				try {
					const hookPayload = this.hookPayloads[rawKey];
					hookPayload.providerVersion = providerVersion;
					await this.hooks("onConnect", {
						...hookPayload,
						documentName
					}, (contextAdditions) => {
						hookPayload.context = {
							...hookPayload.context,
							...contextAdditions
						};
					});
					await this.hooks("onAuthenticate", {
						token,
						...hookPayload,
						documentName
					}, (contextAdditions) => {
						hookPayload.context = {
							...hookPayload.context,
							...contextAdditions
						};
					});
					hookPayload.connectionConfig.isAuthenticated = true;
					const message = new OutgoingMessage(responseAddress).writeAuthenticated(hookPayload.connectionConfig.readOnly);
					this.websocket.send(message.toUint8Array());
					await this.setUpNewConnection(rawKey, documentName, sessionId);
				} catch (err) {
					const error = err || Forbidden;
					const message = new OutgoingMessage(responseAddress).writePermissionDenied(error.reason ?? "permission-denied");
					this.websocket.send(message.toUint8Array());
					this.documentConnectionsEstablished.delete(rawKey);
					delete this.hookPayloads[rawKey];
					delete this.incomingMessageQueue[rawKey];
				}
			} catch (error) {
				console.error(error);
				this.websocket.close(ResetConnection.code, ResetConnection.reason);
			}
		};
		this.handleMessage = (data) => {
			this.lastMessageReceivedAt = Date.now();
			try {
				const tmpMsg = new IncomingMessage(data);
				const rawKey = decoding.readVarString(tmpMsg.decoder);
				const sepIdx = rawKey.indexOf("\0");
				const documentName = sepIdx === -1 ? rawKey : rawKey.substring(0, sepIdx);
				const connection = this.documentConnections[rawKey] ?? this.documentConnections[documentName];
				if (connection) {
					connection.handleMessage(data);
					return;
				}
				if (this.incomingMessageQueue[rawKey] === void 0) {
					this.incomingMessageQueue[rawKey] = [];
					if (this.hookPayloads[rawKey]) throw new Error("first message, but hookPayloads exists");
					this.hookPayloads[rawKey] = {
						instance: this.documentProvider,
						request: this.request,
						connectionConfig: {
							readOnly: false,
							isAuthenticated: false
						},
						requestHeaders: this.request.headers,
						requestParameters: getParameters(this.request),
						socketId: this.socketId,
						context: { ...this.defaultContext },
						providerVersion: null
					};
				}
				this.handleQueueingMessage(data, rawKey, documentName);
			} catch (closeError) {
				console.error(closeError);
				this.websocket.close(Unauthorized.code, Unauthorized.reason);
			}
		};
		this.timeout = opts.timeout;
		this.pingInterval = setInterval(this.check, this.timeout);
	}
	/**
	* Handle WebSocket close event. Call this from your integration
	* when the WebSocket connection closes.
	*/
	handleClose(event) {
		this.close(event);
		clearInterval(this.pingInterval);
	}
	close(event) {
		Object.values(this.documentConnections).forEach((connection) => connection.close(event));
	}
	/**
	* Set a callback that will be triggered when the connection is closed
	*/
	onClose(callback) {
		this.callbacks.onClose.push(callback);
		return this;
	}
	/**
	* Create a new connection by the given request and document
	*/
	createConnection(connection, document, hookPayload, sessionId, providerVersion) {
		const instance = new Connection(connection, hookPayload.request, document, hookPayload.socketId, hookPayload.context, hookPayload.connectionConfig.readOnly, sessionId, providerVersion);
		instance.onClose(async (document, event) => {
			await instance.waitForPendingMessages();
			const disconnectHookPayload = {
				instance: this.documentProvider,
				clientsCount: document.getConnectionsCount(),
				context: hookPayload.context,
				document,
				socketId: hookPayload.socketId,
				documentName: document.name,
				requestHeaders: hookPayload.request.headers,
				requestParameters: getParameters(hookPayload.request)
			};
			await this.hooks("onDisconnect", disconnectHookPayload);
			this.callbacks.onClose.forEach((callback) => callback(document, disconnectHookPayload));
		});
		instance.onStatelessCallback(async (payload) => {
			try {
				return await this.hooks("onStateless", payload);
			} catch (error) {
				if (error?.message) throw error;
			}
		});
		instance.beforeHandleMessage((connection, update) => {
			const beforeHandleMessagePayload = {
				instance: this.documentProvider,
				clientsCount: document.getConnectionsCount(),
				context: hookPayload.context,
				document,
				socketId: hookPayload.socketId,
				connection,
				documentName: document.name,
				requestHeaders: hookPayload.request.headers,
				requestParameters: getParameters(hookPayload.request),
				update
			};
			return this.hooks("beforeHandleMessage", beforeHandleMessagePayload);
		});
		instance.beforeSync((connection, payload) => {
			const beforeSyncPayload = {
				clientsCount: document.getConnectionsCount(),
				context: hookPayload.context,
				document,
				documentName: document.name,
				connection,
				type: payload.type,
				payload: payload.payload
			};
			return this.hooks("beforeSync", beforeSyncPayload);
		});
		return instance;
	}
};

//#endregion
//#region packages/server/src/DirectConnection.ts
var DirectConnection = class {
	/**
	* Constructor.
	*/
	constructor(document, instance, context) {
		this.document = null;
		this.document = document;
		this.instance = instance;
		this.context = context ?? {};
		this.document.addDirectConnection();
	}
	async transact(transaction) {
		if (!this.document) throw new Error("direct connection closed");
		this.document.transact((x) => {
			transaction(this.document);
		}, {
			source: "local",
			context: this.context
		});
	}
	async disconnect() {
		if (this.document) {
			this.document?.removeDirectConnection();
			await this.instance.storeDocumentHooks(this.document, {
				clientsCount: this.document.getConnectionsCount(),
				lastContext: this.context,
				lastTransactionOrigin: {
					source: "local",
					context: this.context
				},
				document: this.document,
				documentName: this.document.name,
				instance: this.instance
			}, true);
			if (this.document.getConnectionsCount() === 0 && !this.document.saveMutex.isLocked()) {
				await this.instance.hooks("onDisconnect", {
					instance: this.instance,
					clientsCount: this.document.getConnectionsCount(),
					context: this.context,
					document: this.document,
					socketId: "server",
					documentName: this.document.name,
					requestHeaders: new Headers(),
					requestParameters: new URLSearchParams$1()
				});
				await this.instance.unloadDocument(this.document);
			}
			this.document = null;
		}
	}
};

//#endregion
//#region packages/server/src/util/debounce.ts
const useDebounce = () => {
	const timers = /* @__PURE__ */ new Map();
	const runningExecutions = /* @__PURE__ */ new Map();
	const debounce = async (id, func, debounce, maxDebounce) => {
		const old = timers.get(id);
		const start = old?.start || Date.now();
		const run = async () => {
			if (runningExecutions.has(id)) await runningExecutions.get(id);
			timers.delete(id);
			const execution = func();
			runningExecutions.set(id, execution);
			const executionResult = await execution;
			runningExecutions.delete(id);
			return executionResult;
		};
		if (old?.timeout) clearTimeout(old.timeout);
		if (debounce === 0) return run();
		if (Date.now() - start >= maxDebounce) return run();
		timers.set(id, {
			start,
			timeout: setTimeout(run, debounce),
			func: run
		});
	};
	const executeNow = (id) => {
		const old = timers.get(id);
		if (old) {
			clearTimeout(old.timeout);
			return old.func();
		}
	};
	const isDebounced = (id) => {
		return timers.has(id);
	};
	const isCurrentlyExecuting = (id) => {
		return runningExecutions.has(id);
	};
	return {
		debounce,
		isDebounced,
		isCurrentlyExecuting,
		executeNow
	};
};

//#endregion
//#region packages/server/src/Hocuspocus.ts
const defaultConfiguration = {
	name: null,
	timeout: 6e4,
	debounce: 2e3,
	maxDebounce: 1e4,
	quiet: false,
	yDocOptions: {
		gc: true,
		gcFilter: () => true
	},
	unloadImmediately: true
};
var Hocuspocus = class {
	constructor(configuration) {
		this.configuration = {
			...defaultConfiguration,
			extensions: [],
			onConfigure: () => new Promise((r) => r(null)),
			onListen: () => new Promise((r) => r(null)),
			onUpgrade: () => new Promise((r) => r(null)),
			onConnect: () => new Promise((r) => r(null)),
			connected: () => new Promise((r) => r(null)),
			beforeHandleMessage: () => new Promise((r) => r(null)),
			beforeHandleAwareness: () => new Promise((r) => r()),
			beforeSync: () => new Promise((r) => r(null)),
			beforeBroadcastStateless: () => new Promise((r) => r(null)),
			onStateless: () => new Promise((r) => r(null)),
			onChange: () => new Promise((r) => r(null)),
			onCreateDocument: () => new Promise((r) => r(null)),
			onLoadDocument: () => new Promise((r) => r(null)),
			onStoreDocument: () => new Promise((r) => r(null)),
			afterStoreDocument: () => new Promise((r) => r(null)),
			onAwarenessUpdate: () => new Promise((r) => r(null)),
			onRequest: () => new Promise((r) => r(null)),
			onDisconnect: () => new Promise((r) => r(null)),
			onDestroy: () => new Promise((r) => r(null))
		};
		this.loadingDocuments = /* @__PURE__ */ new Map();
		this.unloadingDocuments = /* @__PURE__ */ new Map();
		this.documents = /* @__PURE__ */ new Map();
		this.debouncer = useDebounce();
		if (configuration) this.configure(configuration);
	}
	/**
	* Configure Hocuspocus
	*/
	configure(configuration) {
		this.configuration = {
			...this.configuration,
			...configuration
		};
		this.configuration.extensions.sort((a, b) => {
			const one = typeof a.priority === "undefined" ? 100 : a.priority;
			const two = typeof b.priority === "undefined" ? 100 : b.priority;
			if (one > two) return -1;
			if (one < two) return 1;
			return 0;
		});
		this.configuration.extensions.push({
			onConfigure: this.configuration.onConfigure,
			onListen: this.configuration.onListen,
			onUpgrade: this.configuration.onUpgrade,
			onConnect: this.configuration.onConnect,
			connected: this.configuration.connected,
			onAuthenticate: this.configuration.onAuthenticate,
			onTokenSync: this.configuration.onTokenSync,
			onLoadDocument: this.configuration.onLoadDocument,
			afterLoadDocument: this.configuration.afterLoadDocument,
			beforeHandleMessage: this.configuration.beforeHandleMessage,
			beforeHandleAwareness: this.configuration.beforeHandleAwareness,
			beforeBroadcastStateless: this.configuration.beforeBroadcastStateless,
			beforeSync: this.configuration.beforeSync,
			onStateless: this.configuration.onStateless,
			onChange: this.configuration.onChange,
			onStoreDocument: this.configuration.onStoreDocument,
			afterStoreDocument: this.configuration.afterStoreDocument,
			onAwarenessUpdate: this.configuration.onAwarenessUpdate,
			onRequest: this.configuration.onRequest,
			beforeUnloadDocument: this.configuration.beforeUnloadDocument,
			afterUnloadDocument: this.configuration.afterUnloadDocument,
			onDisconnect: this.configuration.onDisconnect,
			onDestroy: this.configuration.onDestroy
		});
		this.hooks("onConfigure", {
			configuration: this.configuration,
			version,
			instance: this
		});
		return this;
	}
	/**
	* Get the total number of active documents
	*/
	getDocumentsCount() {
		return this.documents.size;
	}
	/**
	* Get the total number of active connections
	*/
	getConnectionsCount() {
		const uniqueSocketIds = /* @__PURE__ */ new Set();
		const totalDirectConnections = Array.from(this.documents.values()).reduce((acc, document) => {
			document.getConnections().forEach(({ socketId }) => {
				uniqueSocketIds.add(socketId);
			});
			return acc + document.directConnectionsCount;
		}, 0);
		return uniqueSocketIds.size + totalDirectConnections;
	}
	/**
	* Immediately execute all pending debounced onStoreDocument calls.
	* Useful during shutdown to ensure documents are persisted and unloaded
	* before the server exits, even when unloadImmediately is false.
	*/
	flushPendingStores() {
		this.documents.forEach((document) => {
			const debounceId = `onStoreDocument-${document.name}`;
			if (!document.isLoading && this.debouncer.isDebounced(debounceId)) this.debouncer.executeNow(debounceId);
		});
	}
	/**
	* Force close one or more connections
	*/
	closeConnections(documentName) {
		this.documents.forEach((document) => {
			if (documentName && document.name !== documentName) return;
			document.connections.forEach((_clients, connection) => {
				connection.close(ResetConnection);
			});
		});
	}
	/**
	* The `handleConnection` method receives incoming WebSocket connections,
	* runs all hooks:
	*
	*  - onConnect for all connections
	*  - onAuthenticate only if required
	*
	* … and if nothing fails it'll fully establish the connection and
	* load the Document then.
	*/
	handleConnection(incoming, request, defaultContext = {}) {
		const clientConnection = new ClientConnection(incoming, request, this, this.hooks.bind(this), { timeout: this.configuration.timeout }, defaultContext);
		clientConnection.onClose((document, hookPayload) => {
			if (document.getConnectionsCount() > 0) return;
			if (!document.isLoading && this.debouncer.isDebounced(`onStoreDocument-${document.name}`)) {
				if (this.configuration.unloadImmediately) this.debouncer.executeNow(`onStoreDocument-${document.name}`);
			} else this.unloadDocument(document);
		});
		return clientConnection;
	}
	/**
	* Handle update of the given document
	*
	* "connection" is not necessarily type "Connection", it's the Yjs "origin" (which is "Connection" if
	* the update is incoming from the provider, but can be anything if the updates is originated from an extension.
	*/
	handleDocumentUpdate(document, origin, update) {
		const connection = isTransactionOrigin(origin) && origin.source === "connection" ? origin.connection : void 0;
		const request = connection?.request;
		const context = isTransactionOrigin(origin) ? origin.source === "connection" ? origin.connection.context : origin.source === "local" ? origin.context ?? {} : {} : {};
		const changePayload = {
			instance: this,
			clientsCount: document.getConnectionsCount(),
			document,
			documentName: document.name,
			requestHeaders: request?.headers ?? new Headers(),
			requestParameters: getParameters(request),
			socketId: connection?.socketId ?? "",
			update,
			transactionOrigin: origin,
			connection,
			context
		};
		this.hooks("onChange", changePayload);
		if (shouldSkipStoreHooks(origin)) return;
		const storePayload = {
			instance: this,
			clientsCount: document.getConnectionsCount(),
			document,
			lastContext: context,
			lastTransactionOrigin: origin,
			documentName: document.name
		};
		this.storeDocumentHooks(document, storePayload);
	}
	/**
	* Create a new document by the given request
	*/
	async createDocument(documentName, request, socketId, connection, context) {
		if (!documentName.trim()) throw new Error("Document name must not be empty");
		const existingLoadingDoc = this.loadingDocuments.get(documentName);
		if (existingLoadingDoc) return existingLoadingDoc;
		const existingDoc = this.documents.get(documentName);
		if (existingDoc) return Promise.resolve(existingDoc);
		const loadDocPromise = this.loadDocument(documentName, request, socketId, connection, context);
		this.loadingDocuments.set(documentName, loadDocPromise);
		try {
			const doc = await loadDocPromise;
			this.documents.set(documentName, doc);
			this.loadingDocuments.delete(documentName);
			return doc;
		} catch (e) {
			this.loadingDocuments.delete(documentName);
			throw e;
		}
	}
	async loadDocument(documentName, request, socketId, connectionConfig, context) {
		const requestHeaders = request.headers;
		const requestParameters = getParameters(request);
		const resolvedContext = context ?? {};
		const yDocOptions = await this.hooks("onCreateDocument", {
			documentName,
			requestHeaders,
			requestParameters,
			connectionConfig,
			context: resolvedContext,
			socketId,
			instance: this
		});
		const document = new Document(documentName, {
			...this.configuration.yDocOptions,
			...yDocOptions
		});
		const hookPayload = {
			instance: this,
			context: resolvedContext,
			connectionConfig,
			document,
			documentName,
			socketId,
			requestHeaders,
			requestParameters
		};
		try {
			await this.hooks("onLoadDocument", hookPayload, (loadedDocument) => {
				if (loadedDocument instanceof Doc) applyUpdate(document, encodeStateAsUpdate(loadedDocument));
				else if (loadedDocument instanceof Uint8Array) applyUpdate(document, loadedDocument);
			});
		} catch (e) {
			this.closeConnections(documentName);
			this.unloadDocument(document);
			throw e;
		}
		document.isLoading = false;
		document.onUpdate((document, origin, update) => {
			document.lastChangeTime = Date.now();
			this.handleDocumentUpdate(document, origin, update);
		});
		await this.hooks("afterLoadDocument", hookPayload);
		document.beforeBroadcastStateless((document, stateless) => {
			const hookPayload = {
				document,
				documentName: document.name,
				payload: stateless
			};
			this.hooks("beforeBroadcastStateless", hookPayload);
		});
		document.beforeHandleAwareness((document, states, transactionOrigin) => {
			const connection = isTransactionOrigin(transactionOrigin) && transactionOrigin.source === "connection" ? transactionOrigin.connection : void 0;
			const request = connection?.request;
			return this.hooks("beforeHandleAwareness", {
				awareness: document.awareness,
				clientsCount: document.getConnectionsCount(),
				context: connection?.context,
				document,
				documentName: document.name,
				instance: this,
				requestHeaders: request?.headers ?? new Headers(),
				requestParameters: request ? getParameters(request) : new URLSearchParams(),
				socketId: connection?.socketId ?? "",
				transactionOrigin,
				connection,
				states
			});
		});
		document.awareness.on("update", (update, origin) => {
			this.hooks("onAwarenessUpdate", {
				document,
				documentName,
				instance: this,
				...update,
				transactionOrigin: origin,
				connection: isTransactionOrigin(origin) && origin.source === "connection" ? origin.connection : void 0,
				awareness: document.awareness,
				states: awarenessStatesToArray(document.awareness.getStates())
			});
		});
		return document;
	}
	storeDocumentHooks(document, hookPayload, immediately) {
		const debounceId = `onStoreDocument-${document.name}`;
		return this.debouncer.debounce(debounceId, async () => {
			try {
				await document.saveMutex.runExclusive(async () => {
					await this.hooks("onStoreDocument", hookPayload);
					await this.hooks("afterStoreDocument", hookPayload);
				});
			} catch (error) {
				if (error instanceof SkipFurtherHooksError) {
					setTimeout(() => {
						if (this.shouldUnloadDocument(document)) this.unloadDocument(document);
					}, 0);
					return;
				}
				console.error("Caught error during storeDocumentHooks. Document stays in memory to avoid data loss", error);
				return;
			}
			setTimeout(() => {
				if (this.shouldUnloadDocument(document)) this.unloadDocument(document);
			}, 0);
		}, immediately ? 0 : this.configuration.debounce, this.configuration.maxDebounce);
	}
	/**
	* Run the given hook on all configured extensions.
	* Runs the given callback after each hook.
	*/
	hooks(name, payload, callback = null) {
		const { extensions } = this.configuration;
		let chain = Promise.resolve();
		extensions.filter((extension) => typeof extension[name] === "function").forEach((extension) => {
			chain = chain.then(() => extension[name]?.(payload)).catch((error) => {
				if (error?.message) console.error(`[${name}]`, error.message);
				throw error;
			});
			if (callback) chain = chain.then((...args) => callback(...args));
		});
		return chain;
	}
	shouldUnloadDocument(document) {
		return (this.debouncer.isDebounced(`onStoreDocument-${document.name}`) || this.debouncer.isCurrentlyExecuting(`onStoreDocument-${document.name}`) || document.saveMutex.isLocked()) === false && document.getConnectionsCount() === 0;
	}
	async unloadDocument(document) {
		const documentName = document.name;
		if (!this.shouldUnloadDocument(document)) return;
		if (!this.documents.has(documentName)) return;
		if (this.unloadingDocuments.has(documentName)) return this.unloadingDocuments.get(documentName);
		const actualUnloadingLogic = async () => {
			try {
				await this.hooks("beforeUnloadDocument", {
					instance: this,
					documentName,
					document
				});
			} catch (e) {
				return;
			}
			if (!this.shouldUnloadDocument(document)) return;
			this.documents.delete(documentName);
			document.destroy();
			await this.hooks("afterUnloadDocument", {
				instance: this,
				documentName
			});
		};
		const unloading = actualUnloadingLogic();
		this.unloadingDocuments.set(documentName, Promise.resolve(unloading));
		await unloading;
		this.unloadingDocuments.delete(documentName);
	}
	async openDirectConnection(documentName, context) {
		return new DirectConnection(await this.createDocument(documentName, new Request("http://localhost"), crypto.randomUUID(), {
			isAuthenticated: true,
			readOnly: false
		}, context), this, context);
	}
};

//#endregion
//#region packages/server/src/Server.ts
const defaultServerConfiguration = {
	port: 80,
	address: "0.0.0.0",
	stopOnSignals: true
};
var Server = class {
	constructor(configuration) {
		this.configuration = {
			...defaultConfiguration,
			...defaultServerConfiguration,
			extensions: []
		};
		this.setupHttpUpgrade = () => {
			this.httpServer.on("upgrade", async (request, socket, head) => {
				try {
					await this.hocuspocus.hooks("onUpgrade", {
						request,
						socket,
						head,
						instance: this.hocuspocus
					});
					this.crossws.handleUpgrade(request, socket, head);
				} catch (error) {
					if (error) throw error;
				}
			});
		};
		this.requestHandler = async (request, response) => {
			try {
				await this.hocuspocus.hooks("onRequest", {
					request,
					response,
					instance: this.hocuspocus
				});
				response.writeHead(200, { "Content-Type": "text/plain" });
				response.end("Welcome to Hocuspocus!");
			} catch (error) {
				if (error) throw error;
			}
		};
		if (configuration) this.configuration = {
			...this.configuration,
			...configuration
		};
		this.hocuspocus = new Hocuspocus(this.configuration);
		this.hocuspocus.server = this;
		this.httpServer = createServer(this.requestHandler);
		this.crossws = crossws({
			serverOptions: this.configuration.websocketOptions,
			hooks: {
				open: (peer) => {
					peer._hocuspocus = this.hocuspocus.handleConnection(peer.websocket, peer.request);
				},
				message: (peer, message) => {
					peer._hocuspocus?.handleMessage(message.uint8Array());
				},
				close: (peer, event) => {
					peer._hocuspocus?.handleClose({
						code: event.code,
						reason: event.reason
					});
				},
				error: (peer, error) => {
					console.error("WebSocket error for peer:", peer.id);
					console.error(error);
				}
			}
		});
		this.setupHttpUpgrade();
	}
	async listen(port, callback = null) {
		if (port) this.configuration.port = port;
		if (typeof callback === "function") this.hocuspocus.configuration.extensions.push({ onListen: callback });
		if (this.configuration.stopOnSignals) {
			const signalHandler = async () => {
				await this.destroy();
				process.exit(0);
			};
			process.on("SIGINT", signalHandler);
			process.on("SIGQUIT", signalHandler);
			process.on("SIGTERM", signalHandler);
		}
		return new Promise((resolve, reject) => {
			this.httpServer.listen({
				port: this.configuration.port,
				address: this.configuration.address
			}, async () => {
				if (!this.configuration.quiet && String("development") !== "testing") this.showStartScreen();
				const onListenPayload = {
					instance: this.hocuspocus,
					configuration: this.configuration,
					port: this.address.port
				};
				try {
					await this.hocuspocus.hooks("onListen", onListenPayload);
					resolve(this.hocuspocus);
				} catch (e) {
					reject(e);
				}
			});
		});
	}
	get address() {
		return this.httpServer.address() || {
			port: this.configuration.port,
			address: this.configuration.address,
			family: "IPv4"
		};
	}
	async destroy() {
		await new Promise((resolve) => {
			this.httpServer.close();
			try {
				this.configuration.extensions.push({ async afterUnloadDocument({ instance }) {
					if (instance.getDocumentsCount() === 0) resolve();
				} });
				if (this.hocuspocus.getDocumentsCount() === 0) resolve();
				this.hocuspocus.closeConnections();
				this.hocuspocus.flushPendingStores();
			} catch (error) {
				console.error(error);
			}
		});
		await this.hocuspocus.hooks("onDestroy", { instance: this.hocuspocus });
	}
	get URL() {
		return `${this.configuration.address}:${this.address.port}`;
	}
	get webSocketURL() {
		return `ws://${this.URL}`;
	}
	get httpURL() {
		return `http://${this.URL}`;
	}
	showStartScreen() {
		const name = this.configuration.name ? ` (${this.configuration.name})` : "";
		console.log();
		console.log(`  ${kleur.cyan(`Hocuspocus v${version}${name}`)}${kleur.green(" running at:")}`);
		console.log();
		console.log(`  > HTTP: ${kleur.cyan(`${this.httpURL}`)}`);
		console.log(`  > WebSocket: ${this.webSocketURL}`);
		const extensions = this.configuration?.extensions.map((extension) => {
			return extension.extensionName ?? extension.constructor?.name;
		}).filter((name) => name).filter((name) => name !== "Object");
		if (!extensions.length) return;
		console.log();
		console.log("  Extensions:");
		extensions.forEach((name) => {
			console.log(`  - ${name}`);
		});
		console.log();
		console.log(`  ${kleur.green("Ready.")}`);
		console.log();
	}
};

//#endregion
export { Connection, Document, Hocuspocus, IncomingMessage, MessageReceiver, MessageType, OutgoingMessage, Server, defaultConfiguration, defaultServerConfiguration, isTransactionOrigin, shouldSkipStoreHooks, useDebounce };
//# sourceMappingURL=hocuspocus-server.esm.js.map