Skip to content

Commit 95b63fb

Browse files
committed
feat: add watchers
1 parent 0929617 commit 95b63fb

2 files changed

Lines changed: 75 additions & 25 deletions

File tree

README.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,40 @@ const client = createReplaneClient({
3636
baseUrl: "https://api.my-replane-host.com",
3737
});
3838

39-
const featureFlag = await client.getConfig<boolean>({
39+
// One-off fetch
40+
const featureFlag = await client.getConfigValue<boolean>({
4041
name: "new-onboarding",
4142
fallback: false,
4243
});
4344

44-
// or a more complex example
45-
45+
// Typed example
4646
interface PasswordRequirements {
4747
minLength: number;
4848
requireSymbol: boolean;
4949
}
5050

51-
const passwordRequirements = await client.getConfig<PasswordRequirements>({
51+
const passwordRequirements = await client.getConfigValue<PasswordRequirements>({
5252
name: "password-requirements",
5353
fallback: { minLength: 8, requireSymbol: false },
5454
});
55+
56+
// Watching a config
57+
const billingEnabled = await client.watchConfigValue<boolean>({
58+
name: "billing-enabled",
59+
fallback: false,
60+
});
61+
62+
// Later, read the latest value
63+
if (billingEnabled.get()) {
64+
console.log("Billing enabled!");
65+
}
5566
```
5667

5768
## API
5869

5970
### `createReplaneClient(options)`
6071

61-
Returns an object: `{ getConfig }`.
72+
Returns an object: `{ getConfigValue, watchConfigValue }`.
6273

6374
#### Options
6475

@@ -68,21 +79,27 @@ Returns an object: `{ getConfig }`.
6879
- `timeoutMs` (number) – abort the request after N ms. Default: 1000.
6980
- `logger` (`{ info(...), error(...) }`) – optional logger (defaults to `console`).
7081

71-
### `client.getConfig({ name, fallback, ...overrides })`
82+
### `client.getConfigValue({ name, fallback, ...overrides })`
7283

7384
Parameters:
7485

7586
- `name` (string) – config name to fetch.
7687
- `fallback` (any) – value returned when request fails or response is invalid.
7788
- Overrides: `baseUrl`, `apiKey`, `fetchFn`, `timeoutMs`, `logger` – same semantics as in `createReplaneClient`.
7889

79-
Returns: the config value.
90+
Returns: the config value (or the provided fallback on failure).
8091

8192
Failures (non-2xx, network error, or invalid JSON) do not throw; the function logs via `logger.error(...)` and returns your `fallback`.
8293

94+
### `client.watchConfigValue({ name, fallback, ...overrides })`
95+
96+
Creates a lightweight watcher that refreshes the config value in the background. Useful for long‑lived processes wanting near‑real‑time updates without manually refetching.
97+
98+
Returns a promise resolving to `{ get(): T }` where `get()` returns the current value. Until the first successful fetch it returns the provided fallback. Errors during refresh reuse the last known value.
99+
83100
### Errors
84101

85-
This SDK doesn't throw on request/response errors during `getConfig`. Instead, it logs (using the provided or default logger) and returns the provided `fallback`.
102+
This SDK doesn't throw on request/response errors during `getConfigValue` or background refreshes in `watchConfigValue`. Instead, it logs (using the provided or default logger) and returns the provided `fallback` (or previous value for watchers).
86103

87104
## Environment notes
88105

@@ -98,7 +115,7 @@ interface LayoutConfig {
98115
variant: "a" | "b";
99116
ttl: number;
100117
}
101-
const layout = await client.getConfig<LayoutConfig>({
118+
const layout = await client.getConfigValue<LayoutConfig>({
102119
name: "layout",
103120
fallback: { variant: "a", ttl: 0 },
104121
});
@@ -107,7 +124,7 @@ const layout = await client.getConfig<LayoutConfig>({
107124
Timeout override:
108125

109126
```ts
110-
await client.getConfig({
127+
await client.getConfigValue({
111128
name: "slow-config",
112129
fallback: null,
113130
timeoutMs: 1500,

src/index.ts

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,17 @@ export interface GetConfigRequest<T> extends Partial<ReplaneClientOptions> {
5959
fallback: T;
6060
}
6161

62+
export interface ConfigValueWatcher<T> {
63+
/** Current config value (or fallback if not found). */
64+
get(): T;
65+
}
66+
6267
export interface ReplaneClient {
6368
/** Fetch a config value by name. */
64-
getConfig<T = unknown>(req: GetConfigRequest<T>): Promise<T | undefined>;
69+
getConfigValue<T = unknown>(req: GetConfigRequest<T>): Promise<T | undefined>;
70+
watchConfigValue<T = unknown>(
71+
req: GetConfigRequest<T>
72+
): Promise<ConfigValueWatcher<T>>;
6573
}
6674

6775
/**
@@ -75,21 +83,46 @@ export function createReplaneClient(
7583
): ReplaneClient {
7684
if (!sdkOptions.apiKey) throw new Error("API key is required");
7785

86+
async function getConfigValue<T = unknown>(
87+
req: GetConfigRequest<T>
88+
): Promise<T> {
89+
if (!req.name) throw new Error("config name is required");
90+
const finalOptions = combineOptions(sdkOptions, req);
91+
try {
92+
return await _getConfig<T>({
93+
configName: req.name,
94+
fallback: req.fallback,
95+
options: finalOptions,
96+
});
97+
} catch (err: unknown) {
98+
finalOptions.logger.error("ReplaneClient.getConfig error", err);
99+
return req.fallback;
100+
}
101+
}
102+
103+
async function watchConfigValue<T = unknown>(
104+
originalReq: GetConfigRequest<T>
105+
): Promise<ConfigValueWatcher<T>> {
106+
const req = { ...originalReq };
107+
let currentValue: T = await getConfigValue<T>(req);
108+
109+
setInterval(async () => {
110+
currentValue = await getConfigValue<T>({
111+
...req,
112+
fallback: currentValue,
113+
});
114+
}, 60_000);
115+
116+
return {
117+
get() {
118+
return currentValue;
119+
},
120+
};
121+
}
122+
78123
return {
79-
async getConfig<T = unknown>(req: GetConfigRequest<T>): Promise<T> {
80-
if (!req.name) throw new Error("config name is required");
81-
const finalOptions = combineOptions(sdkOptions, req);
82-
try {
83-
return await _getConfig<T>({
84-
configName: req.name,
85-
fallback: req.fallback,
86-
options: finalOptions,
87-
});
88-
} catch (err: unknown) {
89-
finalOptions.logger.error("ReplaneClient.getConfig error", err);
90-
return req.fallback;
91-
}
92-
},
124+
getConfigValue,
125+
watchConfigValue,
93126
};
94127
}
95128

0 commit comments

Comments
 (0)