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