Skip to content

Commit aa7867a

Browse files
committed
feat: add connection lifecycle callbacks
1 parent e039985 commit aa7867a

5 files changed

Lines changed: 412 additions & 16 deletions

File tree

packages/sdk/src/client-types.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,21 @@ export interface ReplaneClientOptions<T extends object> {
182182
* Defaults to SDK identifier (e.g., "replane-js/x.y.z").
183183
*/
184184
agent?: string;
185+
186+
/**
187+
* Callback invoked when a connection error occurs during SSE streaming.
188+
* This is called on each retry attempt, useful for tracking connection health.
189+
* The SDK will automatically retry connections, so this is informational.
190+
* @param error - The error that caused the connection failure
191+
*/
192+
onConnectionError?: (error: unknown) => void;
193+
194+
/**
195+
* Callback invoked when the SSE connection is successfully established.
196+
* This is called on initial connection and on every successful reconnection.
197+
* Useful for tracking connection health and reconnection metrics.
198+
*/
199+
onConnected?: () => void;
185200
}
186201

187202
/**
@@ -234,6 +249,19 @@ export interface RestoreReplaneClientOptions<T extends object> {
234249
* Defaults to SDK identifier (e.g., "replane-js/x.y.z").
235250
*/
236251
agent?: string;
252+
/**
253+
* Callback invoked when a connection error occurs during SSE streaming.
254+
* This is called on each retry attempt, useful for tracking connection health.
255+
* The SDK will automatically retry connections, so this is informational.
256+
* @param error - The error that caused the connection failure
257+
*/
258+
onConnectionError?: (error: unknown) => void;
259+
/**
260+
* Callback invoked when the SSE connection is successfully established.
261+
* This is called on initial connection and on every successful reconnection.
262+
* Useful for tracking connection health and reconnection metrics.
263+
*/
264+
onConnected?: () => void;
237265
};
238266
/**
239267
* Override the context from the snapshot.
@@ -257,4 +285,6 @@ export interface ReplaneFinalOptions {
257285
requiredConfigs: string[];
258286
fallbacks: ConfigDto[];
259287
agent: string;
288+
onConnectionError?: (error: unknown) => void;
289+
onConnected?: () => void;
260290
}

packages/sdk/src/client.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -383,35 +383,37 @@ async function createReplaneClientInternal<T extends object = Record<string, unk
383383
/**
384384
* Convert user options to final options with defaults
385385
*/
386-
function toFinalOptions<T extends object>(defaults: ReplaneClientOptions<T>): ReplaneFinalOptions {
386+
function toFinalOptions<T extends object>(options: ReplaneClientOptions<T>): ReplaneFinalOptions {
387387
return {
388-
sdkKey: defaults.sdkKey ?? "",
389-
baseUrl: (defaults.baseUrl ?? "").replace(/\/+$/, ""),
388+
sdkKey: options.sdkKey ?? "",
389+
baseUrl: (options.baseUrl ?? "").replace(/\/+$/, ""),
390390
fetchFn:
391-
defaults.fetchFn ??
391+
options.fetchFn ??
392392
// some browsers require binding the fetch function to window
393393
globalThis.fetch.bind(globalThis),
394-
requestTimeoutMs: defaults.requestTimeoutMs ?? 2000,
395-
initializationTimeoutMs: defaults.initializationTimeoutMs ?? 5000,
396-
inactivityTimeoutMs: defaults.inactivityTimeoutMs ?? 30_000,
397-
logger: defaults.logger ?? console,
398-
retryDelayMs: defaults.retryDelayMs ?? 200,
394+
requestTimeoutMs: options.requestTimeoutMs ?? 2000,
395+
initializationTimeoutMs: options.initializationTimeoutMs ?? 5000,
396+
inactivityTimeoutMs: options.inactivityTimeoutMs ?? 30_000,
397+
logger: options.logger ?? console,
398+
retryDelayMs: options.retryDelayMs ?? 200,
399399
context: {
400-
...(defaults.context ?? {}),
400+
...(options.context ?? {}),
401401
},
402-
requiredConfigs: Array.isArray(defaults.required)
403-
? defaults.required.map((name) => String(name))
404-
: Object.entries(defaults.required ?? {})
402+
requiredConfigs: Array.isArray(options.required)
403+
? options.required.map((name) => String(name))
404+
: Object.entries(options.required ?? {})
405405
.filter(([_, value]) => value !== undefined)
406406
.map(([name]) => name),
407-
fallbacks: Object.entries(defaults.fallbacks ?? {})
407+
fallbacks: Object.entries(options.fallbacks ?? {})
408408
.filter(([_, value]) => value !== undefined)
409409
.map(([name, value]) => ({
410410
name,
411411
overrides: [],
412412
version: -1,
413413
value,
414414
})),
415-
agent: defaults.agent ?? DEFAULT_AGENT,
415+
agent: options.agent ?? DEFAULT_AGENT,
416+
onConnectionError: options.onConnectionError,
417+
onConnected: options.onConnected,
416418
};
417419
}

packages/sdk/src/storage.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface StartReplicationStreamOptions extends ReplaneFinalOptions {
1717
getBody: () => StartReplicationStreamBody;
1818
signal?: AbortSignal;
1919
onConnect?: () => void;
20+
onConnectionError?: (error: unknown) => void;
21+
onConnected?: () => void;
2022
}
2123

2224
/**
@@ -69,6 +71,9 @@ export class ReplaneRemoteStorage implements ReplaneStorage {
6971
error
7072
);
7173

74+
// Call the connection error callback if provided
75+
options.onConnectionError?.(error);
76+
7277
await retryDelay(retryDelayMs);
7378
}
7479
}
@@ -112,6 +117,8 @@ export class ReplaneRemoteStorage implements ReplaneStorage {
112117
onConnect: () => {
113118
resetInactivityTimer();
114119
options.onConnect?.();
120+
// Call the user's onConnected callback
121+
options.onConnected?.();
115122
},
116123
});
117124

packages/sdk/src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// Auto-generated - do not edit manually
2-
export const VERSION = "0.8.10";
2+
export const VERSION = "0.8.11";
33
export const DEFAULT_AGENT = `replane-js-sdk/${VERSION}`;

0 commit comments

Comments
 (0)