"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.MeshSrpcServer = void 0;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-server-foundation/type-metadata-runtime"));
const SrpcServer_1 = require("../../srpc/SrpcServer");
const logger_1 = require("../logger");
const mesh_client_service_1 = require("./mesh-client-service");
const types_1 = require("./types");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":22,\"type\":{\"kind\":8}},{\"kind\":22,\"type\":{\"kind\":3}},{\"kind\":12,\"types\":[{\"kind\":10,\"literal\":\"disconnect\"},{\"kind\":10,\"literal\":\"conflict\"},{\"kind\":10,\"literal\":\"supersede\"},{\"kind\":10,\"literal\":\"timeout\"},{\"kind\":10,\"literal\":\"badArg\"}],\"typeName\":\"SrpcDisconnectCause\"},{\"kind\":3},{\"kind\":6},{\"kind\":2,\"typeName\":\"(clientId: string, metadata: TRegistryMeta) =\\u003e void | Promise\\u003cvoid\\u003e\"},{\"kind\":2,\"typeName\":\"(nodeId: number, clients: RegisteredClient\\u003cTRegistryMeta\\u003e[]) =\\u003e void | Promise\\u003cvoid\\u003e\"},{\"kind\":2,\"typeName\":\"K\"},{\"kind\":2,\"typeName\":\"(data: TBroadcasts[K], senderInstanceId: number) =\\u003e void | Promise\\u003cvoid\\u003e\"},{\"kind\":2,\"typeName\":\"TBroadcasts[K]\"},{\"kind\":18,\"typeName\":\"MeshBroadcastOptions\",\"types\":[{\"kind\":20,\"name\":\"skipSelf\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true}]},{\"kind\":1},{\"kind\":7},{\"kind\":22,\"type\":{\"kind\":1}}]]", []);
let _MeshSrpcServerMetadata_1;
// --- MeshSrpcServer ---
class MeshSrpcServer extends SrpcServer_1.SrpcServer {
    meshClientService;
    meshLogger = (0, logger_1.createLogger)(this);
    extractMetadataFn;
    connectedCallbacks = new Set();
    disconnectedCallbacks = new Set();
    orphanedCallbacks = new Set();
    // Track metadata for connect/disconnect callbacks.
    clientMetadata = new Map();
    lifecycleConnectedStreams = new WeakSet();
    // Serialize registry mutations per client to prevent race conditions
    // without letting slow user callbacks block reconnects.
    clientRegistryChains = new Map();
    clientCallbackChains = new Map();
    // Microtask-debounced sync tracking
    pendingSyncs = new Set();
    constructor(options) {
        super(options);
        this.extractMetadataFn = options.extractMetadata;
        // Cast needed: MeshClientServiceOptions doesn't carry TBroadcasts,
        // but the broadcast generic only affects registerBroadcastHandler/broadcast
        // which are type-safe at the call site.
        this.meshClientService = new mesh_client_service_1.MeshClientService({
            key: options.meshKey,
            meshOptions: options.meshOptions,
            registryBackend: options.registryBackend,
            clientInvokeFn: async (clientId, type, data, timeoutMs) => {
                const stream = this.streamsByClientId.get(clientId);
                if (!stream) {
                    throw new types_1.ClientDisconnectedError(clientId);
                }
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                return super.invoke(stream, type, data, timeoutMs);
            },
            clientUpdateMetaFn: (clientId, metadata) => {
                const stream = this.streamsByClientId.get(clientId);
                if (!stream)
                    return false;
                // Merge registry metadata onto stream.meta.
                // The proxy traps fire scheduleSyncStreamMeta for each set,
                // but microtask debounce batches them into a single Redis update.
                const meta = stream.meta;
                const update = metadata;
                for (const key of Object.keys(update)) {
                    meta[key] = update[key];
                }
                return true;
            }
        });
        // Wire up cross-pod duplicate detection: disconnect local stream when
        // the same client connects on a different node.
        this.meshClientService.onClientSuperseded(async (clientId) => {
            const stream = this.getCurrentStreamByClientId(clientId);
            if (stream) {
                this.meshLogger.info('Disconnecting superseded client', { clientId });
                this.cleanupStream(stream, 'supersede');
            }
        });
        // Wire up mesh node cleanup callback
        this.meshClientService.onNodeClientsOrphaned(async (nodeId, orphaned) => {
            for (const cb of this.orphanedCallbacks) {
                try {
                    await cb(nodeId, orphaned);
                }
                catch (err) {
                    this.meshLogger.warn('orphaned callback error', { err, nodeId });
                }
            }
        });
    }
    ////////////////////////////////////////
    // Post-establish check - reserve mesh ownership before activation
    /**
     * Defers stream activation until mesh reservation succeeds.
     * Installs meta proxy and reserves the client atomically in Redis
     * (respecting allowSupersede for v2), all serialized in the per-client
     * registry chain. Reserved clients remain hidden from lookup/invoke until
     * onStreamActivated promotes them to active.
     *
     * If registration returns a conflict, cleanupStream is called
     * (which fires onStreamDisconnected and drains the queue) so
     * no connection handlers or RPCs ever run on the rejected stream.
     */
    postEstablishCheck(stream) {
        // Install proxy before anything can mutate meta (even though
        // onStreamConnected hasn't fired yet, code may hold a reference).
        this.installMetaProxy(stream);
        const metadata = snapshotMetadata(this.extractMeta(stream));
        this.clientMetadata.set(stream.clientId, metadata);
        const allowSupersede = !(stream.protocolVersion >= 2 && !stream.supersede);
        return this.enqueueClientRegistry(stream.clientId, async () => {
            // Stream cleaned up during queue wait (disconnect / reconnect)
            if (stream.lastPingAt < 0 || !this.isCurrentStream(stream)) {
                return true;
            }
            const registered = await this.meshClientService.reserveClient(stream.clientId, metadata, allowSupersede);
            if (!registered) {
                this.meshLogger.warn('Rejecting stream due to cross-pod conflict', {
                    streamId: stream.id,
                    clientId: stream.clientId
                });
                this.cleanupStream(stream, 'conflict');
                return true;
            }
            return false;
        });
    }
    ////////////////////////////////////////
    // Lifecycle overrides - connection handlers + mesh callbacks
    extractMeta(stream) {
        return this.extractMetadataFn ? this.extractMetadataFn(stream) : stream.meta;
    }
    static PROXIED = Symbol('proxied');
    /**
     * Install a Proxy on stream.meta that schedules a microtask-debounced
     * sync to Redis whenever any property is mutated.
     *
     * This means handler code, connection handlers, and external code
     * (e.g. FreeSwitch controller) can all mutate stream.meta directly
     * and the mesh registry stays in sync - no manual sync calls needed.
     *
     * **Limitation:** Only top-level property mutations are tracked.
     * Nested mutations (e.g. `stream.meta.user.name = 'Bob'`) do NOT
     * trigger a sync. For nested metadata, either reassign the top-level
     * property (`stream.meta.user = { ...stream.meta.user, name: 'Bob' }`)
     * or call `updateClientMetadata()` explicitly.
     */
    installMetaProxy(stream) {
        // Guard against double-proxy (e.g. meshStart backfill after postEstablishCheck)
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        if (stream.meta[MeshSrpcServer.PROXIED])
            return;
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const self = this;
        const clientId = stream.clientId;
        const proxied = new Proxy(stream.meta, {
            get(target, prop) {
                if (prop === MeshSrpcServer.PROXIED)
                    return true;
                return target[prop];
            },
            set(target, prop, value) {
                target[prop] = value;
                self.scheduleSyncStreamMeta(clientId, stream);
                return true;
            },
            deleteProperty(target, prop) {
                delete target[prop];
                self.scheduleSyncStreamMeta(clientId, stream);
                return true;
            }
        });
        // Replace meta with proxied version.
        // stream is a plain object, so this is safe despite the readonly type.
        stream.meta = proxied;
    }
    /**
     * Schedule a microtask-debounced sync for a client.
     * Multiple synchronous mutations are batched into a single sync.
     */
    scheduleSyncStreamMeta(clientId, stream) {
        if (this.pendingSyncs.has(clientId))
            return;
        this.pendingSyncs.add(clientId);
        queueMicrotask(() => {
            this.pendingSyncs.delete(clientId);
            // Only sync if this stream is still current for this client.
            if (this.isCurrentStream(stream)) {
                this.syncStreamMeta(stream);
            }
        });
    }
    async onStreamConnected(stream) {
        // Run user-registered connection handlers after the initial ping.
        // They may mutate stream.meta; the proxy will sync those changes.
        await super.onStreamConnected(stream);
    }
    async onStreamActivated(stream) {
        const metadata = snapshotMetadata(this.extractMeta(stream));
        this.clientMetadata.set(stream.clientId, metadata);
        if (this.meshInstanceId !== 0) {
            const activated = await this.enqueueClientRegistry(stream.clientId, async () => {
                if (stream.lastPingAt < 0 || this.streamsByClientId.get(stream.clientId) !== stream) {
                    return false;
                }
                return this.meshClientService.activateClient(stream.clientId, metadata);
            });
            if (!activated) {
                this.meshLogger.warn('client activation missing mesh reservation', {
                    streamId: stream.id,
                    clientId: stream.clientId
                });
                this.cleanupStream(stream, 'disconnect');
                return;
            }
        }
        this.syncStreamMeta(stream);
        await this.enqueueClientCallback(stream.clientId, async () => {
            // Skip stale connection callbacks for streams that disconnected
            // or were replaced before activation finished.
            if (stream.lastPingAt < 0 || this.streamsByClientId.get(stream.clientId) !== stream) {
                return;
            }
            if (!this.clientMetadata.has(stream.clientId)) {
                this.meshLogger.warn('client metadata missing during activation', {
                    streamId: stream.id,
                    clientId: stream.clientId
                });
                return;
            }
            this.lifecycleConnectedStreams.add(stream);
            const metadata = this.clientMetadata.get(stream.clientId);
            for (const cb of this.connectedCallbacks) {
                try {
                    await cb(stream.clientId, metadata);
                }
                catch (err) {
                    this.meshLogger.warn('client connected callback error', {
                        err,
                        clientId: stream.clientId
                    });
                }
            }
        });
    }
    onStreamDisconnected(stream, cause) {
        super.onStreamDisconnected(stream, cause);
        const publishedLifecycle = this.lifecycleConnectedStreams.has(stream);
        this.lifecycleConnectedStreams.delete(stream);
        void this.enqueueClientRegistry(stream.clientId, async () => {
            // If a replacement stream is already connected, this is a stale
            // disconnect (same-node reconnect). Skip unregister and callbacks,
            // and leave clientMetadata intact for the new stream.
            const currentStream = this.getCurrentStreamByClientId(stream.clientId);
            if (currentStream && currentStream !== stream) {
                return;
            }
            const hasMetadata = this.clientMetadata.has(stream.clientId);
            const metadata = this.clientMetadata.get(stream.clientId);
            const removed = await this.meshClientService.unregisterClient(stream.clientId);
            if (removed && hasMetadata && publishedLifecycle) {
                this.clientMetadata.delete(stream.clientId);
                void this.enqueueClientCallback(stream.clientId, async () => {
                    for (const cb of this.disconnectedCallbacks) {
                        try {
                            await cb(stream.clientId, metadata);
                        }
                        catch (err) {
                            this.meshLogger.warn('client disconnected callback error', {
                                err,
                                clientId: stream.clientId
                            });
                        }
                    }
                });
            }
            else if (removed && publishedLifecycle) {
                this.meshLogger.warn('client metadata missing during disconnect cleanup', {
                    streamId: stream.id,
                    clientId: stream.clientId
                });
                this.clientMetadata.delete(stream.clientId);
            }
            else {
                this.clientMetadata.delete(stream.clientId);
            }
        });
    }
    ////////////////////////////////////////
    // Meta sync
    /**
     * Sync the current stream.meta to the mesh registry.
     * Called automatically by the meta proxy's microtask debounce.
     * Routed through enqueueClientRegistry so updates are serialized
     * after initial registration (prevents lost updates if registration
     * hasn't completed yet).
     */
    syncStreamMeta(stream) {
        // Snapshot the current metadata so we compare values, not references.
        // Without this, the default path (no extractMetadataFn) returns the
        // same proxied object stored in clientMetadata, so shallowChanged
        // would always return false.
        const metadata = snapshotMetadata(this.extractMeta(stream));
        const existing = this.clientMetadata.get(stream.clientId);
        if (existing && !shallowChanged(existing, metadata))
            return;
        this.clientMetadata.set(stream.clientId, metadata);
        // Write directly to the registry (always local/owning node).
        // Do NOT route through meshClientService.updateClientMetadata here -
        // that would loop back into clientUpdateMetaFn -> stream.meta -> proxy.
        void this.enqueueClientRegistry(stream.clientId, async () => {
            await this.clientRegistry.updateMetadata(stream.clientId, metadata);
        });
    }
    ////////////////////////////////////////
    // Client lifecycle serialization
    enqueueClientRegistry(clientId, fn) {
        const prev = this.clientRegistryChains.get(clientId) ?? Promise.resolve();
        const next = prev.then(fn, fn);
        const safeNext = next.then(() => undefined, err => {
            this.meshLogger.warn('client registry error', { err, clientId });
        });
        const chain = safeNext.finally(() => {
            // Clean up the chain entry if it's still ours
            if (this.clientRegistryChains.get(clientId) === chain) {
                this.clientRegistryChains.delete(clientId);
            }
        });
        this.clientRegistryChains.set(clientId, chain);
        return next;
    }
    enqueueClientCallback(clientId, fn) {
        const prev = this.clientCallbackChains.get(clientId) ?? Promise.resolve();
        const next = prev.then(fn, fn);
        const safeNext = next.then(() => undefined, err => {
            this.meshLogger.warn('client callback error', { err, clientId });
        });
        const chain = safeNext.finally(() => {
            if (this.clientCallbackChains.get(clientId) === chain) {
                this.clientCallbackChains.delete(clientId);
            }
        });
        this.clientCallbackChains.set(clientId, chain);
        return next;
    }
    ////////////////////////////////////////
    // Public API
    get meshInstanceId() {
        return this.meshClientService.instanceId;
    }
    get clientRegistry() {
        return this.meshClientService.clientRegistry;
    }
    /**
     * Update metadata for a client, regardless of which node owns it.
     * Routes through the mesh to the owning node so that stream.meta
     * reflects the change immediately and the proxy auto-syncs to Redis.
     * For local streams, you can also mutate stream.meta directly.
     */
    async updateClientMetadata(clientId, metadata) {
        // Set clientMetadata eagerly so the proxy's deferred syncStreamMeta
        // sees shallowChanged=false and skips the redundant Redis write.
        const previous = this.clientMetadata.get(clientId);
        this.clientMetadata.set(clientId, snapshotMetadata(metadata));
        const updated = await this.meshClientService.updateClientMetadata(clientId, metadata);
        if (!updated) {
            // Restore on failure
            if (previous !== undefined) {
                this.clientMetadata.set(clientId, previous);
            }
            else {
                this.clientMetadata.delete(clientId);
            }
        }
        return updated;
    }
    onClientConnected(handler) {
        this.connectedCallbacks.add(handler);
    }
    onClientDisconnected(handler) {
        this.disconnectedCallbacks.add(handler);
    }
    onNodeClientsOrphaned(handler) {
        this.orphanedCallbacks.add(handler);
    }
    registerBroadcastHandler(type, handler) {
        this.meshClientService.registerBroadcastHandler(type, handler);
    }
    async broadcast(type, data, options) {
        return this.meshClientService.broadcast(type, data, options);
    }
    /**
     * Invoke a client method across any node in the mesh.
     * Overloaded: when called with a stream, delegates to SrpcServer.invoke.
     * When called with a clientId string, routes through the mesh.
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    invoke(streamOrClientId, prefix, data, timeoutMs) {
        if (typeof streamOrClientId === 'string') {
            return this.meshClientService.invoke(streamOrClientId, prefix, data, timeoutMs);
        }
        return super.invoke(streamOrClientId, prefix, data, timeoutMs);
    }
    async meshStart() {
        await this.meshClientService.start();
        // Backfill clients that connected before mesh tracking was running.
        // Route through enqueueClientRegistry so backfill registrations are
        // serialized with any concurrent disconnect for the same clientId.
        const backfillStreams = new Map();
        for (const [clientId, stream] of this.pendingStreamsByClientId) {
            backfillStreams.set(clientId, stream);
        }
        for (const [clientId, stream] of this.streamsByClientId) {
            if (!backfillStreams.has(clientId)) {
                backfillStreams.set(clientId, stream);
            }
        }
        const backfillPromises = [];
        for (const [clientId, stream] of backfillStreams) {
            // Install proxy if not already proxied (streams that connected before meshStart)
            this.installMetaProxy(stream);
            if (!this.clientMetadata.has(clientId)) {
                const metadata = snapshotMetadata(this.extractMeta(stream));
                this.clientMetadata.set(clientId, metadata);
            }
            const metadata = this.clientMetadata.get(clientId);
            const allowSupersede = !(stream.protocolVersion >= 2 && !stream.supersede);
            void this.enqueueClientRegistry(clientId, async () => {
                // Only backfill the current stream (active or pending).
                const currentStream = this.getCurrentStreamByClientId(clientId);
                if (currentStream !== stream)
                    return;
                const registered = stream.isActivated
                    ? await this.meshClientService.registerClient(clientId, metadata, allowSupersede)
                    : await this.meshClientService.reserveClient(clientId, metadata, allowSupersede);
                if (!registered) {
                    this.meshLogger.warn('Backfill rejected: cross-pod conflict', { clientId });
                    this.cleanupStream(stream, 'conflict');
                }
            });
            const chain = this.clientRegistryChains.get(clientId);
            if (chain)
                backfillPromises.push(chain);
        }
        await Promise.all(backfillPromises);
    }
    async meshStop() {
        await this.meshClientService.stop();
        this.clientMetadata.clear();
    }
    static __tsfType = _MeshSrpcServerMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"MeshSrpcServer\",\"typeName\":\"MeshSrpcServer\",\"classType\":{\"$tsf\":0},\"properties\":[],\"methods\":[{\"name\":\"postEstablishCheck\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":18,\"typeName\":\"SrpcStream\",\"types\":[{\"kind\":20,\"name\":\"byteStream\",\"type\":{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false}]},\"optional\":false},{\"kind\":20,\"name\":\"$ws\",\"type\":{\"kind\":16,\"typeName\":\"WebSocket\",\"classType\":{\"$tsf\":1}},\"optional\":false},{\"kind\":20,\"name\":\"$queue\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":2},\"typeArguments\":[{\"kind\":6},{\"kind\":18,\"typeName\":\"IQueuedRequest\",\"types\":[{\"kind\":20,\"name\":\"exp\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"resolve\",\"type\":{\"kind\":2,\"typeName\":\"(value: unknown) =\\u003e void\"},\"optional\":false},{\"kind\":20,\"name\":\"reject\",\"type\":{\"kind\":2,\"typeName\":\"(err: unknown) =\\u003e void\"},\"optional\":false}]}]},\"optional\":false},{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientStreamId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"address\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"appVersion\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"configureTs\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"protocolVersion\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"supersede\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"meta\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false},{\"kind\":20,\"name\":\"connectedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"isActivated\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"lastPingAt\",\"type\":{\"kind\":7},\"optional\":false}]},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":0},\"description\":\"//////////////////////////////////////// // Post-establish check - reserve mesh ownership before activation\"},{\"name\":\"onStreamConnected\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":18,\"typeName\":\"SrpcStream\",\"types\":[{\"kind\":20,\"name\":\"byteStream\",\"type\":{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false}]},\"optional\":false},{\"kind\":20,\"name\":\"$ws\",\"type\":{\"kind\":16,\"typeName\":\"WebSocket\",\"classType\":{\"$tsf\":1}},\"optional\":false},{\"kind\":20,\"name\":\"$queue\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":2},\"typeArguments\":[{\"kind\":6},{\"kind\":18,\"typeName\":\"IQueuedRequest\",\"types\":[{\"kind\":20,\"name\":\"exp\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"resolve\",\"type\":{\"kind\":2,\"typeName\":\"(value: unknown) =\\u003e void\"},\"optional\":false},{\"kind\":20,\"name\":\"reject\",\"type\":{\"kind\":2,\"typeName\":\"(err: unknown) =\\u003e void\"},\"optional\":false}]}]},\"optional\":false},{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientStreamId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"address\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"appVersion\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"configureTs\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"protocolVersion\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"supersede\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"meta\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false},{\"kind\":20,\"name\":\"connectedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"isActivated\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"lastPingAt\",\"type\":{\"kind\":7},\"optional\":false}]},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"onStreamActivated\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":18,\"typeName\":\"SrpcStream\",\"types\":[{\"kind\":20,\"name\":\"byteStream\",\"type\":{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false}]},\"optional\":false},{\"kind\":20,\"name\":\"$ws\",\"type\":{\"kind\":16,\"typeName\":\"WebSocket\",\"classType\":{\"$tsf\":1}},\"optional\":false},{\"kind\":20,\"name\":\"$queue\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":2},\"typeArguments\":[{\"kind\":6},{\"kind\":18,\"typeName\":\"IQueuedRequest\",\"types\":[{\"kind\":20,\"name\":\"exp\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"resolve\",\"type\":{\"kind\":2,\"typeName\":\"(value: unknown) =\\u003e void\"},\"optional\":false},{\"kind\":20,\"name\":\"reject\",\"type\":{\"kind\":2,\"typeName\":\"(err: unknown) =\\u003e void\"},\"optional\":false}]}]},\"optional\":false},{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientStreamId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"address\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"appVersion\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"configureTs\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"protocolVersion\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"supersede\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"meta\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false},{\"kind\":20,\"name\":\"connectedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"isActivated\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"lastPingAt\",\"type\":{\"kind\":7},\"optional\":false}]},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"onStreamDisconnected\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":18,\"typeName\":\"SrpcStream\",\"types\":[{\"kind\":20,\"name\":\"byteStream\",\"type\":{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false}]},\"optional\":false},{\"kind\":20,\"name\":\"$ws\",\"type\":{\"kind\":16,\"typeName\":\"WebSocket\",\"classType\":{\"$tsf\":1}},\"optional\":false},{\"kind\":20,\"name\":\"$queue\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":2},\"typeArguments\":[{\"kind\":6},{\"kind\":18,\"typeName\":\"IQueuedRequest\",\"types\":[{\"kind\":20,\"name\":\"exp\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"resolve\",\"type\":{\"kind\":2,\"typeName\":\"(value: unknown) =\\u003e void\"},\"optional\":false},{\"kind\":20,\"name\":\"reject\",\"type\":{\"kind\":2,\"typeName\":\"(err: unknown) =\\u003e void\"},\"optional\":false}]}]},\"optional\":false},{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientStreamId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"address\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"appVersion\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"configureTs\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"protocolVersion\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"supersede\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"meta\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false},{\"kind\":20,\"name\":\"connectedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"isActivated\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"lastPingAt\",\"type\":{\"kind\":7},\"optional\":false}]},\"optional\":false,\"default\":false},{\"name\":\"cause\",\"type\":{\"$tsfType\":2},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"updateClientMetadata\",\"parameters\":[{\"name\":\"clientId\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false},{\"name\":\"metadata\",\"type\":{\"kind\":16,\"typeName\":\"TRegistryMeta\",\"classType\":{\"$tsf\":4}},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":0},\"description\":\"Update metadata for a client, regardless of which node owns it. Routes through the mesh to the owning node so that stream.meta reflects the change immediately and the proxy auto-syncs to Redis. For local streams, you can also mutate stream.meta directly.\"},{\"name\":\"onClientConnected\",\"parameters\":[{\"name\":\"handler\",\"type\":{\"$tsfType\":5},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"onClientDisconnected\",\"parameters\":[{\"name\":\"handler\",\"type\":{\"$tsfType\":5},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"onNodeClientsOrphaned\",\"parameters\":[{\"name\":\"handler\",\"type\":{\"$tsfType\":6},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"registerBroadcastHandler\",\"parameters\":[{\"name\":\"type\",\"type\":{\"$tsfType\":7},\"optional\":false,\"default\":false},{\"name\":\"handler\",\"type\":{\"$tsfType\":8},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"broadcast\",\"parameters\":[{\"name\":\"type\",\"type\":{\"$tsfType\":7},\"optional\":false,\"default\":false},{\"name\":\"data\",\"type\":{\"$tsfType\":9},\"optional\":false,\"default\":false},{\"name\":\"options\",\"type\":{\"$tsfType\":10},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"invoke\",\"parameters\":[{\"name\":\"streamOrClientId\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"SrpcStream\",\"types\":[{\"kind\":20,\"name\":\"byteStream\",\"type\":{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false}]},\"optional\":false},{\"kind\":20,\"name\":\"$ws\",\"type\":{\"kind\":16,\"typeName\":\"WebSocket\",\"classType\":{\"$tsf\":1}},\"optional\":false},{\"kind\":20,\"name\":\"$queue\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":2},\"typeArguments\":[{\"kind\":6},{\"kind\":18,\"typeName\":\"IQueuedRequest\",\"types\":[{\"kind\":20,\"name\":\"exp\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"resolve\",\"type\":{\"kind\":2,\"typeName\":\"(value: unknown) =\\u003e void\"},\"optional\":false},{\"kind\":20,\"name\":\"reject\",\"type\":{\"kind\":2,\"typeName\":\"(err: unknown) =\\u003e void\"},\"optional\":false}]}]},\"optional\":false},{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientStreamId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"address\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"appVersion\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"configureTs\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"protocolVersion\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"supersede\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"meta\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false},{\"kind\":20,\"name\":\"connectedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"isActivated\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"lastPingAt\",\"type\":{\"kind\":7},\"optional\":false}]},{\"kind\":6}]},\"optional\":false,\"default\":false},{\"name\":\"prefix\",\"type\":{\"$tsfType\":11},\"optional\":false,\"default\":false},{\"name\":\"data\",\"type\":{\"$tsfType\":11},\"optional\":false,\"default\":false},{\"name\":\"timeoutMs\",\"type\":{\"$tsfType\":12},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":13},\"description\":\"Invoke a client method across any node in the mesh. Overloaded: when called with a stream, delegates to SrpcServer.invoke. When called with a clientId string, routes through the mesh.\"},{\"name\":\"meshStart\",\"parameters\":[],\"returnType\":{\"$tsfType\":1}},{\"name\":\"meshStop\",\"parameters\":[],\"returnType\":{\"$tsfType\":1}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"options\",\"type\":{\"kind\":13,\"types\":[{\"kind\":18,\"typeName\":\"ISrpcServerOptions\",\"types\":[{\"kind\":20,\"name\":\"logger\",\"type\":{\"kind\":18,\"typeName\":\"ISrpcLogger\",\"types\":[]},\"optional\":false},{\"kind\":20,\"name\":\"clientMessage\",\"type\":{\"kind\":18,\"typeName\":\"SrpcMessageFns\",\"types\":[]},\"optional\":false},{\"kind\":20,\"name\":\"serverMessage\",\"type\":{\"kind\":18,\"typeName\":\"SrpcMessageFns\",\"types\":[]},\"optional\":false},{\"kind\":20,\"name\":\"wsPath\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"debug\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"logLevel\",\"type\":{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":10,\"literal\":\"info\"},{\"kind\":10,\"literal\":\"debug\"},{\"kind\":10,\"literal\":false}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"httpServer\",\"type\":{\"kind\":12,\"types\":[{\"kind\":16,\"typeName\":\"Server\",\"classType\":{\"$tsfImport\":[5,\"node:http\",\"Server\"]}},{\"kind\":4}]},\"optional\":true}]},{\"kind\":18,\"typeName\":\"MeshSrpcServerOptions\",\"types\":[{\"kind\":20,\"name\":\"meshKey\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"meshOptions\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"MeshServiceOptions\",\"types\":[{\"kind\":20,\"name\":\"heartbeatIntervalMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"nodeTtlMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"requestTimeoutMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"leaderOptions\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"LeaderServiceOptions\",\"types\":[{\"kind\":20,\"name\":\"ttlMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"renewalIntervalMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"retryDelayMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true}]},{\"kind\":4}]},\"optional\":true}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"registryBackend\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"MeshClientRegistryBackend\",\"types\":[]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"extractMetadata\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"(stream: SrpcStream\\u003cTMeta\\u003e) =\\u003e TRegistryMeta\"},{\"kind\":4}]},\"optional\":true}]}]},\"optional\":false,\"default\":false}]}]", [() => MeshSrpcServer, () => (typeof WebSocket !== "undefined" ? WebSocket : (typeof exports !== "undefined" ? exports.WebSocket : undefined)), () => (typeof Map !== "undefined" ? Map : (typeof exports !== "undefined" ? exports.Map : undefined)), () => (typeof TMeta !== "undefined" ? TMeta : (typeof exports !== "undefined" ? exports.TMeta : undefined)), () => (typeof TRegistryMeta !== "undefined" ? TRegistryMeta : (typeof exports !== "undefined" ? exports.TRegistryMeta : undefined)), require], __tsf_metadata_type);
}
exports.MeshSrpcServer = MeshSrpcServer;
_MeshSrpcServerMetadata_1.classType = MeshSrpcServer;
MeshSrpcServer.__tsfType = _MeshSrpcServerMetadata_1;
////////////////////////////////////////
// Helpers
function snapshotMetadata(meta) {
    if (Array.isArray(meta))
        return [...meta];
    if (!isPlainObject(meta))
        return meta;
    return { ...meta };
}
function isPlainObject(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const proto = Object.getPrototypeOf(value);
    return proto === Object.prototype || proto === null;
}
function shallowChanged(a, b) {
    if (a === b)
        return false;
    if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
        return a !== b;
    const aObj = a;
    const bObj = b;
    const keys = new Set([...Object.keys(aObj), ...Object.keys(bObj)]);
    for (const key of keys) {
        if (aObj[key] !== bObj[key])
            return true;
    }
    return false;
}
exports.__tsfTypeAliases = _a.decodeCompactMetadataV1("[1,{\"MeshSrpcServerOptions\":{\"kind\":18,\"typeName\":\"MeshSrpcServerOptions\",\"types\":[{\"kind\":20,\"name\":\"meshKey\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"meshOptions\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"MeshServiceOptions\",\"types\":[{\"kind\":20,\"name\":\"heartbeatIntervalMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"nodeTtlMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"requestTimeoutMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"leaderOptions\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"LeaderServiceOptions\",\"types\":[{\"kind\":20,\"name\":\"ttlMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"renewalIntervalMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"retryDelayMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true}]},{\"kind\":4}]},\"optional\":true}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"registryBackend\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"MeshClientRegistryBackend\",\"types\":[]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"extractMetadata\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"(stream: SrpcStream\\u003cTMeta\\u003e) =\\u003e TRegistryMeta\"},{\"kind\":4}]},\"optional\":true}]}}]", [], __tsf_metadata_type);
//# sourceMappingURL=mesh-srpc-server.js.map