@replanejs/sdk 0.8.20 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -230,13 +230,13 @@ const SUPPORTED_REPLICATION_STREAM_RECORD_TYPES = Object.keys({
230
230
  * and streams config updates via SSE.
231
231
  */
232
232
  var ReplaneRemoteStorage = class {
233
- closeController = new AbortController();
233
+ disconnectController = new AbortController();
234
234
  /**
235
235
  * Start a replication stream that yields config updates.
236
236
  * This method never throws - it retries on failure with exponential backoff.
237
237
  */
238
238
  async *startReplicationStream(options) {
239
- const { signal, cleanUpSignals } = combineAbortSignals([this.closeController.signal, options.signal]);
239
+ const { signal, cleanUpSignals } = combineAbortSignals([this.disconnectController.signal, options.signal]);
240
240
  try {
241
241
  let failedAttempts = 0;
242
242
  while (!signal.aborted) try {
@@ -252,7 +252,6 @@ var ReplaneRemoteStorage = class {
252
252
  const retryDelayMs = Math.min(options.retryDelayMs * 2 ** (failedAttempts - 1), 1e4);
253
253
  if (!signal.aborted) {
254
254
  options.logger.error(`Failed to fetch project events, retrying in ${retryDelayMs}ms...`, error);
255
- options.onConnectionError?.(error);
256
255
  await retryDelay(retryDelayMs);
257
256
  }
258
257
  }
@@ -289,7 +288,6 @@ var ReplaneRemoteStorage = class {
289
288
  onConnect: () => {
290
289
  resetInactivityTimer();
291
290
  options.onConnect?.();
292
- options.onConnected?.();
293
291
  }
294
292
  });
295
293
  for await (const sseEvent of rawEvents) {
@@ -304,10 +302,11 @@ var ReplaneRemoteStorage = class {
304
302
  }
305
303
  }
306
304
  /**
307
- * Close the storage and abort any active connections
305
+ * Disconnect the storage and abort any active connections
308
306
  */
309
- close() {
310
- this.closeController.abort();
307
+ disconnect() {
308
+ this.disconnectController.abort();
309
+ this.disconnectController = new AbortController();
311
310
  }
312
311
  getAuthHeader(options) {
313
312
  return `Bearer ${options.sdkKey}`;
@@ -483,66 +482,156 @@ function castToContextType(expectedValue, contextValue) {
483
482
 
484
483
  //#endregion
485
484
  //#region src/version.ts
486
- const VERSION = "0.8.20";
485
+ const VERSION = "0.9.2";
487
486
  const DEFAULT_AGENT = `replane-js-sdk/${VERSION}`;
488
487
 
489
488
  //#endregion
490
489
  //#region src/client.ts
491
490
  /**
492
- * Creates the core client logic shared between createReplaneClient and restoreReplaneClient
491
+ * The Replane client for managing dynamic configuration.
492
+ *
493
+ * @example
494
+ * ```typescript
495
+ * // Create client with defaults
496
+ * const client = new Replane({
497
+ * defaults: { myConfig: 'defaultValue' }
498
+ * });
499
+ *
500
+ * // Use immediately (returns defaults)
501
+ * const value = client.get('myConfig');
502
+ *
503
+ * // Connect for real-time updates
504
+ * await client.connect({
505
+ * baseUrl: 'https://app.replane.dev',
506
+ * sdkKey: 'your-sdk-key'
507
+ * });
508
+ * ```
509
+ *
510
+ * @example
511
+ * ```typescript
512
+ * // SSR/Hydration: Create from snapshot
513
+ * const client = new Replane({
514
+ * snapshot: serverSnapshot
515
+ * });
516
+ * await client.connect({ baseUrl, sdkKey });
517
+ * ```
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * // In-memory mode (no connection)
522
+ * const client = new Replane({
523
+ * defaults: { feature: true, limit: 100 }
524
+ * });
525
+ * // Don't call connect() - works entirely in-memory
526
+ * ```
493
527
  */
494
- function createClientCore(options) {
495
- const { initialConfigs, context, logger, storage, streamOptions, requiredConfigs } = options;
496
- const configs = new Map(initialConfigs.map((config) => [config.name, config]));
497
- const clientReady = new Deferred();
498
- const configSubscriptions = new Map();
499
- const clientSubscriptions = new Set();
500
- function processConfigUpdates(updatedConfigs) {
501
- for (const config of updatedConfigs) {
502
- configs.set(config.name, {
503
- name: config.name,
504
- overrides: config.overrides,
505
- value: config.value
506
- });
507
- for (const callback of clientSubscriptions) callback({
508
- name: config.name,
509
- value: config.value
510
- });
511
- for (const callback of configSubscriptions.get(config.name) ?? []) callback({
512
- name: config.name,
513
- value: config.value
528
+ var Replane = class {
529
+ configs;
530
+ context;
531
+ logger;
532
+ storage = null;
533
+ configSubscriptions = new Map();
534
+ clientSubscriptions = new Set();
535
+ /**
536
+ * Create a new Replane client.
537
+ *
538
+ * The client is usable immediately after construction with defaults or snapshot data.
539
+ * Call `connect()` to establish a real-time connection for live updates.
540
+ *
541
+ * @param options - Configuration options
542
+ */
543
+ constructor(options = {}) {
544
+ this.logger = options.logger ?? console;
545
+ this.context = { ...options.context ?? {} };
546
+ const initialConfigs = [];
547
+ if (options.snapshot) for (const config of options.snapshot.configs) initialConfigs.push({
548
+ name: config.name,
549
+ value: config.value,
550
+ overrides: config.overrides
551
+ });
552
+ if (options.defaults) {
553
+ const snapshotNames = new Set(initialConfigs.map((c) => c.name));
554
+ for (const [name, value] of Object.entries(options.defaults)) if (value !== void 0 && !snapshotNames.has(name)) initialConfigs.push({
555
+ name,
556
+ value,
557
+ overrides: []
514
558
  });
515
559
  }
560
+ this.configs = new Map(initialConfigs.map((config) => [config.name, config]));
516
561
  }
517
- async function startStreaming() {
518
- if (!storage || !streamOptions) return;
519
- try {
520
- const replicationStream = storage.startReplicationStream({
521
- ...streamOptions,
522
- getBody: () => ({
523
- currentConfigs: [...configs.values()].map((config) => ({
524
- name: config.name,
525
- overrides: config.overrides,
526
- value: config.value
527
- })),
528
- requiredConfigs
529
- })
530
- });
531
- for await (const event of replicationStream) {
532
- const updatedConfigs = event.type === "config_change" ? [event.config] : event.configs;
533
- processConfigUpdates(updatedConfigs);
534
- clientReady.resolve();
535
- }
536
- } catch (error) {
537
- logger.error("Replane: error in SSE connection:", error);
538
- clientReady.reject(error);
539
- throw error;
562
+ /**
563
+ * Connect to the Replane server for real-time config updates.
564
+ *
565
+ * This method establishes an SSE connection to receive live config updates.
566
+ * If already connected, it will disconnect first and reconnect with new options.
567
+ *
568
+ * @param options - Connection options including baseUrl and sdkKey
569
+ * @returns Promise that resolves when the initial connection is established
570
+ * @throws {ReplaneError} If connection times out and no defaults are available
571
+ *
572
+ * @example
573
+ * ```typescript
574
+ * await client.connect({
575
+ * baseUrl: 'https://app.replane.dev',
576
+ * sdkKey: 'rp_xxx'
577
+ * });
578
+ * ```
579
+ */
580
+ async connect(options) {
581
+ this.disconnect();
582
+ const finalOptions = this.toFinalOptions(options);
583
+ this.storage = new ReplaneRemoteStorage();
584
+ const clientReady = new Deferred();
585
+ this.startStreaming(finalOptions, clientReady);
586
+ const timeoutId = setTimeout(() => {
587
+ this.disconnect();
588
+ clientReady.reject(new ReplaneError({
589
+ message: "Replane client connection timed out",
590
+ code: ReplaneErrorCode.Timeout
591
+ }));
592
+ }, finalOptions.connectTimeoutMs);
593
+ clientReady.promise.finally(() => clearTimeout(timeoutId));
594
+ await clientReady.promise;
595
+ }
596
+ /**
597
+ * Disconnect from the Replane server.
598
+ *
599
+ * Stops the SSE connection and cleans up resources.
600
+ * The client remains usable with cached config values.
601
+ * Can call `connect()` again to reconnect.
602
+ */
603
+ disconnect() {
604
+ if (this.storage) {
605
+ this.storage.disconnect();
606
+ this.storage = null;
540
607
  }
541
608
  }
542
- function get(configName, getConfigOptions = {}) {
543
- const config = configs.get(String(configName));
609
+ /**
610
+ * Get a config value by name.
611
+ *
612
+ * Evaluates any overrides based on the client context and per-call context.
613
+ *
614
+ * @param configName - The name of the config to retrieve
615
+ * @param options - Optional settings for this call
616
+ * @returns The config value
617
+ * @throws {ReplaneError} If config not found and no default provided
618
+ *
619
+ * @example
620
+ * ```typescript
621
+ * // Simple get
622
+ * const value = client.get('myConfig');
623
+ *
624
+ * // With default fallback
625
+ * const value = client.get('myConfig', { default: 'fallback' });
626
+ *
627
+ * // With per-call context for override evaluation
628
+ * const value = client.get('myConfig', { context: { userId: '123' } });
629
+ * ```
630
+ */
631
+ get(configName, options = {}) {
632
+ const config = this.configs.get(String(configName));
544
633
  if (config === void 0) {
545
- if ("default" in getConfigOptions) return getConfigOptions.default;
634
+ if ("default" in options) return options.default;
546
635
  throw new ReplaneError({
547
636
  message: `Config not found: ${String(configName)}`,
548
637
  code: ReplaneErrorCode.NotFound
@@ -550,15 +639,15 @@ function createClientCore(options) {
550
639
  }
551
640
  try {
552
641
  return evaluateOverrides(config.value, config.overrides, {
553
- ...context,
554
- ...getConfigOptions?.context ?? {}
555
- }, logger);
642
+ ...this.context,
643
+ ...options?.context ?? {}
644
+ }, this.logger);
556
645
  } catch (error) {
557
- logger.error(`Replane: error evaluating overrides for config ${String(configName)}:`, error);
646
+ this.logger.error(`Replane: error evaluating overrides for config ${String(configName)}:`, error);
558
647
  return config.value;
559
648
  }
560
649
  }
561
- const subscribe = (callbackOrConfigName, callbackOrUndefined) => {
650
+ subscribe(callbackOrConfigName, callbackOrUndefined) {
562
651
  let configName = void 0;
563
652
  let callback;
564
653
  if (typeof callbackOrConfigName === "function") callback = callbackOrConfigName;
@@ -572,20 +661,38 @@ function createClientCore(options) {
572
661
  originalCallback(...args);
573
662
  };
574
663
  if (configName === void 0) {
575
- clientSubscriptions.add(callback);
664
+ this.clientSubscriptions.add(callback);
576
665
  return () => {
577
- clientSubscriptions.delete(callback);
666
+ this.clientSubscriptions.delete(callback);
578
667
  };
579
668
  }
580
- if (!configSubscriptions.has(configName)) configSubscriptions.set(configName, new Set());
581
- configSubscriptions.get(configName).add(callback);
669
+ if (!this.configSubscriptions.has(configName)) this.configSubscriptions.set(configName, new Set());
670
+ this.configSubscriptions.get(configName).add(callback);
582
671
  return () => {
583
- configSubscriptions.get(configName)?.delete(callback);
584
- if (configSubscriptions.get(configName)?.size === 0) configSubscriptions.delete(configName);
672
+ this.configSubscriptions.get(configName)?.delete(callback);
673
+ if (this.configSubscriptions.get(configName)?.size === 0) this.configSubscriptions.delete(configName);
585
674
  };
586
- };
587
- const getSnapshot = () => ({
588
- configs: [...configs.values()].map((config) => ({
675
+ }
676
+ /**
677
+ * Get a serializable snapshot of the current client state.
678
+ *
679
+ * Useful for SSR/hydration scenarios where you want to pass
680
+ * configs from server to client.
681
+ *
682
+ * @returns Snapshot object that can be serialized to JSON
683
+ *
684
+ * @example
685
+ * ```typescript
686
+ * // On server
687
+ * const snapshot = client.getSnapshot();
688
+ * const json = JSON.stringify(snapshot);
689
+ *
690
+ * // On client
691
+ * const client = new Replane({ snapshot: JSON.parse(json) });
692
+ * ```
693
+ */
694
+ getSnapshot() {
695
+ return { configs: [...this.configs.values()].map((config) => ({
589
696
  name: config.name,
590
697
  value: config.value,
591
698
  overrides: config.overrides.map((override) => ({
@@ -593,196 +700,83 @@ function createClientCore(options) {
593
700
  conditions: override.conditions,
594
701
  value: override.value
595
702
  }))
596
- })),
597
- context
598
- });
599
- const close = () => storage?.close();
600
- const client = {
601
- get,
602
- subscribe,
603
- getSnapshot,
604
- close
605
- };
606
- return {
607
- client,
608
- configs,
609
- startStreaming,
610
- clientReady
611
- };
612
- }
613
- /**
614
- * Create a Replane client bound to an SDK key.
615
- *
616
- * @example
617
- * ```typescript
618
- * const client = await createReplaneClient({
619
- * sdkKey: 'your-sdk-key',
620
- * baseUrl: 'https://app.replane.dev'
621
- * });
622
- * const value = client.get('my-config');
623
- * ```
624
- */
625
- async function createReplaneClient(sdkOptions) {
626
- const storage = new ReplaneRemoteStorage();
627
- return await createReplaneClientInternal(toFinalOptions(sdkOptions), storage);
628
- }
629
- /**
630
- * Create a Replane client that uses in-memory storage.
631
- * Useful for testing or when you have static config values.
632
- *
633
- * @example
634
- * ```typescript
635
- * const client = createInMemoryReplaneClient({ 'my-config': 123 });
636
- * const value = client.get('my-config'); // 123
637
- * ```
638
- */
639
- function createInMemoryReplaneClient(initialData) {
640
- return {
641
- get: (configName, options) => {
642
- const config = initialData[configName];
643
- if (config === void 0) {
644
- if (options && "default" in options) return options.default;
645
- throw new ReplaneError({
646
- message: `Config not found: ${String(configName)}`,
647
- code: ReplaneErrorCode.NotFound
648
- });
703
+ })) };
704
+ }
705
+ /**
706
+ * Check if the client is currently connected.
707
+ */
708
+ get isConnected() {
709
+ return this.storage !== null;
710
+ }
711
+ async startStreaming(options, clientReady) {
712
+ if (!this.storage) return;
713
+ try {
714
+ const replicationStream = this.storage.startReplicationStream({
715
+ ...options,
716
+ logger: this.logger,
717
+ getBody: () => ({
718
+ currentConfigs: [...this.configs.values()].map((config) => ({
719
+ name: config.name,
720
+ overrides: config.overrides,
721
+ value: config.value
722
+ })),
723
+ requiredConfigs: []
724
+ })
725
+ });
726
+ for await (const event of replicationStream) {
727
+ const updatedConfigs = event.type === "config_change" ? [event.config] : event.configs;
728
+ this.processConfigUpdates(updatedConfigs);
729
+ clientReady.resolve();
649
730
  }
650
- return config;
651
- },
652
- subscribe: () => {
653
- return () => {};
654
- },
655
- getSnapshot: () => ({ configs: Object.entries(initialData).map(([name, value]) => ({
656
- name,
657
- value,
658
- overrides: []
659
- })) }),
660
- close: () => {}
661
- };
662
- }
663
- /**
664
- * Restore a Replane client from a snapshot.
665
- * This is useful for SSR/hydration scenarios where the server has already fetched configs.
666
- *
667
- * @example
668
- * ```typescript
669
- * // On the server:
670
- * const serverClient = await createReplaneClient({ ... });
671
- * const snapshot = serverClient.getSnapshot();
672
- * // Pass snapshot to client via props/serialization
673
- *
674
- * // On the client:
675
- * const client = restoreReplaneClient({
676
- * snapshot,
677
- * connection: { sdkKey, baseUrl }
678
- * });
679
- * const value = client.get('my-config');
680
- * ```
681
- */
682
- function restoreReplaneClient(options) {
683
- const { snapshot, connection } = options;
684
- const context = options.context ?? snapshot.context ?? {};
685
- const logger = connection?.logger ?? console;
686
- const initialConfigs = snapshot.configs.map((config) => ({
687
- name: config.name,
688
- value: config.value,
689
- overrides: config.overrides
690
- }));
691
- let storage = null;
692
- let streamOptions = null;
693
- if (connection) {
694
- storage = new ReplaneRemoteStorage();
695
- streamOptions = toFinalOptions(connection);
696
- }
697
- const { client, startStreaming } = createClientCore({
698
- initialConfigs,
699
- context,
700
- logger,
701
- storage,
702
- streamOptions,
703
- requiredConfigs: []
704
- });
705
- if (storage && streamOptions) startStreaming().catch((error) => {
706
- logger.error("Replane: error in restored client SSE connection:", error);
707
- });
708
- return client;
709
- }
710
- /**
711
- * Internal function to create a Replane client with the given options and storage
712
- */
713
- async function createReplaneClientInternal(sdkOptions, storage) {
714
- const { client, configs, startStreaming, clientReady } = createClientCore({
715
- initialConfigs: sdkOptions.defaults,
716
- context: sdkOptions.context,
717
- logger: sdkOptions.logger,
718
- storage,
719
- streamOptions: sdkOptions,
720
- requiredConfigs: sdkOptions.requiredConfigs
721
- });
722
- startStreaming().catch((error) => {
723
- sdkOptions.logger.error("Replane: error initializing client:", error);
724
- });
725
- const initializationTimeoutId = setTimeout(() => {
726
- if (sdkOptions.defaults.length === 0) {
727
- client.close();
728
- clientReady.reject(new ReplaneError({
729
- message: "Replane client initialization timed out",
730
- code: ReplaneErrorCode.Timeout
731
- }));
732
- return;
731
+ } catch (error) {
732
+ this.logger.error("Replane: error in SSE connection:", error);
733
+ clientReady.reject(error);
734
+ throw error;
733
735
  }
734
- const missingRequiredConfigs = [];
735
- for (const requiredConfigName of sdkOptions.requiredConfigs) if (!configs.has(requiredConfigName)) missingRequiredConfigs.push(requiredConfigName);
736
- if (missingRequiredConfigs.length > 0) {
737
- client.close();
738
- clientReady.reject(new ReplaneError({
739
- message: `Required configs are missing: ${missingRequiredConfigs.join(", ")}`,
740
- code: ReplaneErrorCode.NotFound
741
- }));
742
- return;
736
+ }
737
+ processConfigUpdates(updatedConfigs) {
738
+ for (const config of updatedConfigs) {
739
+ this.configs.set(config.name, {
740
+ name: config.name,
741
+ overrides: config.overrides,
742
+ value: config.value
743
+ });
744
+ const change = {
745
+ name: config.name,
746
+ value: config.value
747
+ };
748
+ for (const callback of this.clientSubscriptions) callback(change);
749
+ for (const callback of this.configSubscriptions.get(config.name) ?? []) callback(change);
743
750
  }
744
- clientReady.resolve();
745
- }, sdkOptions.initializationTimeoutMs);
746
- clientReady.promise.then(() => clearTimeout(initializationTimeoutId));
747
- await clientReady.promise;
748
- return client;
749
- }
750
- /**
751
- * Convert user options to final options with defaults
752
- */
753
- function toFinalOptions(options) {
754
- return {
755
- sdkKey: options.sdkKey ?? "",
756
- baseUrl: (options.baseUrl ?? "").replace(/\/+$/, ""),
757
- fetchFn: options.fetchFn ?? globalThis.fetch.bind(globalThis),
758
- requestTimeoutMs: options.requestTimeoutMs ?? 2e3,
759
- initializationTimeoutMs: options.initializationTimeoutMs ?? 5e3,
760
- inactivityTimeoutMs: options.inactivityTimeoutMs ?? 3e4,
761
- logger: options.logger ?? console,
762
- retryDelayMs: options.retryDelayMs ?? 200,
763
- context: { ...options.context ?? {} },
764
- requiredConfigs: Array.isArray(options.required) ? options.required.map((name) => String(name)) : Object.entries(options.required ?? {}).filter(([_, value]) => value !== void 0).map(([name]) => name),
765
- defaults: Object.entries(options.defaults ?? {}).filter(([_, value]) => value !== void 0).map(([name, value]) => ({
766
- name,
767
- overrides: [],
768
- version: -1,
769
- value
770
- })),
771
- agent: options.agent ?? DEFAULT_AGENT,
772
- onConnectionError: options.onConnectionError,
773
- onConnected: options.onConnected
774
- };
775
- }
751
+ }
752
+ toFinalOptions(options) {
753
+ return {
754
+ sdkKey: options.sdkKey,
755
+ baseUrl: (options.baseUrl ?? "").replace(/\/+$/, ""),
756
+ fetchFn: options.fetchFn ?? globalThis.fetch.bind(globalThis),
757
+ requestTimeoutMs: options.requestTimeoutMs ?? 2e3,
758
+ connectTimeoutMs: options.connectTimeoutMs ?? 5e3,
759
+ inactivityTimeoutMs: options.inactivityTimeoutMs ?? 3e4,
760
+ retryDelayMs: options.retryDelayMs ?? 200,
761
+ agent: options.agent ?? DEFAULT_AGENT
762
+ };
763
+ }
764
+ };
776
765
 
777
766
  //#endregion
778
767
  //#region src/snapshot.ts
779
768
  const clientCache = new Map();
769
+ const pendingConnections = new Map();
780
770
  function getCacheKey(options) {
781
771
  return `${options.baseUrl}:${options.sdkKey}`;
782
772
  }
783
773
  function setupCleanupTimeout(cacheKey, keepAliveMs) {
784
774
  return setTimeout(() => {
785
- clientCache.delete(cacheKey);
775
+ const cached = clientCache.get(cacheKey);
776
+ if (cached) {
777
+ cached.client.disconnect();
778
+ clientCache.delete(cacheKey);
779
+ }
786
780
  }, keepAliveMs);
787
781
  }
788
782
  /**
@@ -793,35 +787,54 @@ function setupCleanupTimeout(cacheKey, keepAliveMs) {
793
787
  * @example
794
788
  * ```ts
795
789
  * const snapshot = await getReplaneSnapshot({
796
- * baseUrl: process.env.REPLANE_BASE_URL!,
797
- * sdkKey: process.env.REPLANE_SDK_KEY!,
790
+ * connection: {
791
+ * baseUrl: process.env.REPLANE_BASE_URL!,
792
+ * sdkKey: process.env.REPLANE_SDK_KEY!,
793
+ * },
798
794
  * });
799
795
  * ```
800
796
  */
801
797
  async function getReplaneSnapshot(options) {
802
- const { keepAliveMs = 6e4,...clientOptions } = options;
803
- const cacheKey = getCacheKey(clientOptions);
798
+ const { keepAliveMs = 6e4, logger, context, defaults, connection: connectionOptions } = options;
799
+ if (!connectionOptions) return { configs: [...Object.entries(defaults ?? {}).map(([name, value]) => ({
800
+ name,
801
+ value,
802
+ overrides: []
803
+ }))] };
804
+ const cacheKey = getCacheKey(connectionOptions);
804
805
  const cached = clientCache.get(cacheKey);
805
806
  if (cached) {
806
807
  clearTimeout(cached.timeoutId);
807
808
  cached.timeoutId = setupCleanupTimeout(cacheKey, keepAliveMs);
808
- const client$1 = await cached.clientPromise;
809
+ return cached.client.getSnapshot();
810
+ }
811
+ const pending = pendingConnections.get(cacheKey);
812
+ if (pending) {
813
+ const client$1 = await pending.promise;
809
814
  return client$1.getSnapshot();
810
815
  }
811
- const clientPromise = createReplaneClient(clientOptions);
812
- const entry = {
813
- clientPromise,
814
- timeoutId: setupCleanupTimeout(cacheKey, keepAliveMs)
815
- };
816
- clientCache.set(cacheKey, entry);
817
- const client = await clientPromise;
818
- return client.getSnapshot();
816
+ const client = new Replane({
817
+ logger,
818
+ context,
819
+ defaults
820
+ });
821
+ const connectionPromise = client.connect(connectionOptions).then(() => client);
822
+ pendingConnections.set(cacheKey, { promise: connectionPromise });
823
+ try {
824
+ await connectionPromise;
825
+ const entry = {
826
+ client,
827
+ timeoutId: setupCleanupTimeout(cacheKey, keepAliveMs)
828
+ };
829
+ clientCache.set(cacheKey, entry);
830
+ return client.getSnapshot();
831
+ } finally {
832
+ pendingConnections.delete(cacheKey);
833
+ }
819
834
  }
820
835
 
821
836
  //#endregion
837
+ exports.Replane = Replane;
822
838
  exports.ReplaneError = ReplaneError;
823
839
  exports.ReplaneErrorCode = ReplaneErrorCode;
824
- exports.createInMemoryReplaneClient = createInMemoryReplaneClient;
825
- exports.createReplaneClient = createReplaneClient;
826
- exports.getReplaneSnapshot = getReplaneSnapshot;
827
- exports.restoreReplaneClient = restoreReplaneClient;
840
+ exports.getReplaneSnapshot = getReplaneSnapshot;