@jewel998/config 0.2.6 → 0.2.7
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/client/ConfigClient.js +2 -2
- package/dist/client/RefreshManager.js +5 -1
- package/dist/fetch/TierFetcher.d.ts +4 -4
- package/dist/fetch/TierFetcher.js +3 -3
- package/dist/fetch/batchFetcher.js +2 -2
- package/dist/fetch/projectedFetcher.js +5 -5
- package/dist/initConfig.js +29 -22
- package/dist/loading/optimistic.js +1 -1
- package/dist/loading/pessimistic.js +1 -1
- package/dist/types.d.ts +13 -2
- package/package.json +1 -1
|
@@ -32,7 +32,7 @@ export const buildConfigClient = (internals) => {
|
|
|
32
32
|
batchFetchTriggered = true;
|
|
33
33
|
void (async () => {
|
|
34
34
|
try {
|
|
35
|
-
const result = await withRetry(() => fetcher.fetchAll(), retry);
|
|
35
|
+
const { data: result } = await withRetry(() => fetcher.fetchAll(), retry);
|
|
36
36
|
cache.set("__all__", result, DEFAULT_CACHE_TTL);
|
|
37
37
|
for (const [key, value] of Object.entries(result)) {
|
|
38
38
|
cache.set(key, value, DEFAULT_CACHE_TTL);
|
|
@@ -58,7 +58,7 @@ export const buildConfigClient = (internals) => {
|
|
|
58
58
|
return;
|
|
59
59
|
void (async () => {
|
|
60
60
|
try {
|
|
61
|
-
const result = await fetcher.fetchKeys([key]);
|
|
61
|
+
const { data: result } = await fetcher.fetchKeys([key]);
|
|
62
62
|
for (const [k, v] of Object.entries(result)) {
|
|
63
63
|
cache.set(k, v, DEFAULT_CACHE_TTL);
|
|
64
64
|
data[k] = v;
|
|
@@ -59,7 +59,11 @@ export class RefreshManager {
|
|
|
59
59
|
catch {
|
|
60
60
|
// If version check fails, proceed with full fetch as fallback
|
|
61
61
|
}
|
|
62
|
-
const result = await withRetry(() => fetcher.fetchAll(), retry);
|
|
62
|
+
const { data: result, version: responseVersion } = await withRetry(() => fetcher.fetchAll(), retry);
|
|
63
|
+
// Sync the version from the config response itself — avoids drifting
|
|
64
|
+
// from the value learned by the earlier checkVersion() probe.
|
|
65
|
+
if (responseVersion !== null)
|
|
66
|
+
this.cachedVersion = responseVersion;
|
|
63
67
|
cache.set("__all__", result, DEFAULT_CACHE_TTL);
|
|
64
68
|
for (const [key, value] of Object.entries(result)) {
|
|
65
69
|
cache.set(key, value, DEFAULT_CACHE_TTL);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConfigFetcher } from "../types.js";
|
|
1
|
+
import type { ConfigFetcher, FetchResult } from "../types.js";
|
|
2
2
|
export type FetchTier = "prefetch" | "page" | "idle";
|
|
3
3
|
/**
|
|
4
4
|
* TierFetcher — wraps a ConfigFetcher with three responsibilities:
|
|
@@ -24,12 +24,12 @@ export declare class TierFetcher {
|
|
|
24
24
|
* Fetch a specific set of keys under a given tier.
|
|
25
25
|
* Keys already tracked under any tier are skipped (deduplication).
|
|
26
26
|
*/
|
|
27
|
-
fetchTier(keys: string[], tier: FetchTier): Promise<
|
|
27
|
+
fetchTier(keys: string[], tier: FetchTier): Promise<FetchResult>;
|
|
28
28
|
/**
|
|
29
29
|
* Fetch all remaining keys (idle tier).
|
|
30
30
|
* Only keys not already tracked are added to the idle set.
|
|
31
31
|
*/
|
|
32
|
-
fetchAll(): Promise<
|
|
32
|
+
fetchAll(): Promise<FetchResult>;
|
|
33
33
|
/** Check the remote version (passthrough). */
|
|
34
34
|
checkVersion(): ReturnType<ConfigFetcher["checkVersion"]>;
|
|
35
35
|
/**
|
|
@@ -37,7 +37,7 @@ export declare class TierFetcher {
|
|
|
37
37
|
* Used by doRefresh() — we're intentionally re-fetching already-fetched keys
|
|
38
38
|
* with a new context, so the allFetched deduplication should be bypassed.
|
|
39
39
|
*/
|
|
40
|
-
refetchKeys(keys: string[]): Promise<
|
|
40
|
+
refetchKeys(keys: string[]): Promise<FetchResult>;
|
|
41
41
|
/**
|
|
42
42
|
* Record keys as fetched under a tier WITHOUT performing a network fetch.
|
|
43
43
|
* Used when values are hydrated from persistent storage (the boot gate),
|
|
@@ -36,9 +36,9 @@ export class TierFetcher {
|
|
|
36
36
|
async fetchTier(keys, tier) {
|
|
37
37
|
const toFetch = keys.filter((k) => !this.allFetched.has(k));
|
|
38
38
|
if (toFetch.length === 0)
|
|
39
|
-
return {};
|
|
39
|
+
return { data: {}, version: null };
|
|
40
40
|
const result = await this.fetcher.fetchKeys(toFetch);
|
|
41
|
-
this.recordKeys(Object.keys(result), tier);
|
|
41
|
+
this.recordKeys(Object.keys(result.data), tier);
|
|
42
42
|
return result;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
@@ -47,7 +47,7 @@ export class TierFetcher {
|
|
|
47
47
|
*/
|
|
48
48
|
async fetchAll() {
|
|
49
49
|
const result = await this.fetcher.fetchAll();
|
|
50
|
-
for (const key of Object.keys(result)) {
|
|
50
|
+
for (const key of Object.keys(result.data)) {
|
|
51
51
|
if (!this.allFetched.has(key)) {
|
|
52
52
|
this.fetchedKeys.get("idle").add(key);
|
|
53
53
|
this.allFetched.add(key);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export const createBatchFetcher = (transport) => ({
|
|
2
2
|
async fetchAll() {
|
|
3
3
|
const response = await transport.request("v1/config");
|
|
4
|
-
return response.data;
|
|
4
|
+
return { data: response.data, version: response.version ?? null };
|
|
5
5
|
},
|
|
6
6
|
async fetchKeys(_keys) {
|
|
7
7
|
// Batch mode always fetches everything
|
|
8
8
|
const response = await transport.request("v1/config");
|
|
9
|
-
return response.data;
|
|
9
|
+
return { data: response.data, version: response.version ?? null };
|
|
10
10
|
},
|
|
11
11
|
async checkVersion() {
|
|
12
12
|
const response = await transport.request("v1/version");
|
|
@@ -4,7 +4,7 @@ export const createProjectedFetcher = (transport) => {
|
|
|
4
4
|
return {
|
|
5
5
|
async fetchAll() {
|
|
6
6
|
const response = await transport.request("v1/config");
|
|
7
|
-
return response.data;
|
|
7
|
+
return { data: response.data, version: response.version ?? null };
|
|
8
8
|
},
|
|
9
9
|
async fetchKeys(keys) {
|
|
10
10
|
for (const key of keys) {
|
|
@@ -20,7 +20,7 @@ export const createProjectedFetcher = (transport) => {
|
|
|
20
20
|
const response = await transport.request("v1/config", {
|
|
21
21
|
keys: batchedKeys,
|
|
22
22
|
});
|
|
23
|
-
resolve(response.data);
|
|
23
|
+
resolve({ data: response.data, version: response.version ?? null });
|
|
24
24
|
}
|
|
25
25
|
catch (err) {
|
|
26
26
|
reject(err);
|
|
@@ -32,11 +32,11 @@ export const createProjectedFetcher = (transport) => {
|
|
|
32
32
|
// Return only the keys this caller requested
|
|
33
33
|
const filtered = {};
|
|
34
34
|
for (const key of keys) {
|
|
35
|
-
if (key in result) {
|
|
36
|
-
filtered[key] = result[key];
|
|
35
|
+
if (key in result.data) {
|
|
36
|
+
filtered[key] = result.data[key];
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
return filtered;
|
|
39
|
+
return { data: filtered, version: result.version };
|
|
40
40
|
},
|
|
41
41
|
async checkVersion() {
|
|
42
42
|
const response = await transport.request("v1/version");
|
package/dist/initConfig.js
CHANGED
|
@@ -180,12 +180,12 @@ export function initConfig(options) {
|
|
|
180
180
|
cachedVersion = remote;
|
|
181
181
|
}
|
|
182
182
|
const result = await withRetry(() => tierFetcher.fetchTier(initPrefetch, "prefetch"), retry);
|
|
183
|
-
const fetched = applyResult(result);
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
persistVersion(
|
|
188
|
-
tierFetcher.notifyWaiters(result);
|
|
183
|
+
const fetched = applyResult(result.data);
|
|
184
|
+
// Persist the version delivered alongside these values — no extra
|
|
185
|
+
// /version round trip needed; the /config response carries it.
|
|
186
|
+
if (result.version !== null)
|
|
187
|
+
persistVersion(result.version);
|
|
188
|
+
tierFetcher.notifyWaiters(result.data);
|
|
189
189
|
emitUpdated(fetched, "prefetch");
|
|
190
190
|
}
|
|
191
191
|
catch (err) {
|
|
@@ -214,11 +214,10 @@ export function initConfig(options) {
|
|
|
214
214
|
cachedVersion = remote;
|
|
215
215
|
}
|
|
216
216
|
const result = await tierFetcher.fetchAll();
|
|
217
|
-
const fetched = applyResult(result);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
tierFetcher.notifyWaiters(result);
|
|
217
|
+
const fetched = applyResult(result.data);
|
|
218
|
+
if (result.version !== null)
|
|
219
|
+
persistVersion(result.version);
|
|
220
|
+
tierFetcher.notifyWaiters(result.data);
|
|
222
221
|
emitUpdated(fetched, "idle");
|
|
223
222
|
}
|
|
224
223
|
catch {
|
|
@@ -261,18 +260,22 @@ export function initConfig(options) {
|
|
|
261
260
|
if (pf.length > 0) {
|
|
262
261
|
refetches.push(withRetry(() => tierFetcher.refetchKeys(pf), retry)
|
|
263
262
|
.then((r) => {
|
|
264
|
-
applyResult(r);
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
applyResult(r.data);
|
|
264
|
+
if (r.version !== null)
|
|
265
|
+
persistVersion(r.version);
|
|
266
|
+
tierFetcher.notifyWaiters(r.data);
|
|
267
|
+
allFetched.push(...Object.keys(r.data));
|
|
267
268
|
})
|
|
268
269
|
.catch(() => { }));
|
|
269
270
|
}
|
|
270
271
|
if (page.length > 0) {
|
|
271
272
|
refetches.push(withRetry(() => tierFetcher.refetchKeys(page), retry)
|
|
272
273
|
.then((r) => {
|
|
273
|
-
applyResult(r);
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
applyResult(r.data);
|
|
275
|
+
if (r.version !== null)
|
|
276
|
+
persistVersion(r.version);
|
|
277
|
+
tierFetcher.notifyWaiters(r.data);
|
|
278
|
+
allFetched.push(...Object.keys(r.data));
|
|
276
279
|
})
|
|
277
280
|
.catch(() => { }));
|
|
278
281
|
}
|
|
@@ -280,9 +283,11 @@ export function initConfig(options) {
|
|
|
280
283
|
refetches.push(tierFetcher
|
|
281
284
|
.fetchAll()
|
|
282
285
|
.then((r) => {
|
|
283
|
-
applyResult(r);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
applyResult(r.data);
|
|
287
|
+
if (r.version !== null)
|
|
288
|
+
persistVersion(r.version);
|
|
289
|
+
tierFetcher.notifyWaiters(r.data);
|
|
290
|
+
allFetched.push(...Object.keys(r.data));
|
|
286
291
|
})
|
|
287
292
|
.catch(() => { }));
|
|
288
293
|
}
|
|
@@ -300,8 +305,10 @@ export function initConfig(options) {
|
|
|
300
305
|
void (async () => {
|
|
301
306
|
try {
|
|
302
307
|
const result = await withRetry(() => tierFetcher.fetchTier(keys, "page"), retry);
|
|
303
|
-
const fetched = applyResult(result);
|
|
304
|
-
|
|
308
|
+
const fetched = applyResult(result.data);
|
|
309
|
+
if (result.version !== null)
|
|
310
|
+
persistVersion(result.version);
|
|
311
|
+
tierFetcher.notifyWaiters(result.data);
|
|
305
312
|
emitUpdated(fetched, "page");
|
|
306
313
|
}
|
|
307
314
|
catch (err) {
|
|
@@ -3,7 +3,7 @@ import { DEFAULT_CACHE_TTL } from "../types.js";
|
|
|
3
3
|
const scheduleBackgroundFetch = (ctx) => {
|
|
4
4
|
const doFetch = async () => {
|
|
5
5
|
try {
|
|
6
|
-
const data = await withRetry(() => ctx.fetcher.fetchAll(), ctx.retry);
|
|
6
|
+
const { data } = await withRetry(() => ctx.fetcher.fetchAll(), ctx.retry);
|
|
7
7
|
ctx.cache.set("__all__", data, DEFAULT_CACHE_TTL);
|
|
8
8
|
for (const [key, value] of Object.entries(data)) {
|
|
9
9
|
ctx.cache.set(key, value, DEFAULT_CACHE_TTL);
|
|
@@ -7,7 +7,7 @@ export const executePessimistic = async (ctx) => {
|
|
|
7
7
|
setTimeout(() => reject(new TimeoutError(ctx.timeout)), ctx.timeout);
|
|
8
8
|
});
|
|
9
9
|
try {
|
|
10
|
-
const data = await Promise.race([fetchPromise, timeoutPromise]);
|
|
10
|
+
const { data } = await Promise.race([fetchPromise, timeoutPromise]);
|
|
11
11
|
ctx.cache.set("__all__", data, DEFAULT_CACHE_TTL);
|
|
12
12
|
for (const [key, value] of Object.entries(data)) {
|
|
13
13
|
ctx.cache.set(key, value, DEFAULT_CACHE_TTL);
|
package/dist/types.d.ts
CHANGED
|
@@ -137,11 +137,22 @@ export interface LoadingResult {
|
|
|
137
137
|
export interface LoadingStrategyHandler {
|
|
138
138
|
execute(context: LoadingContext): LoadingResult | Promise<LoadingResult>;
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Result of a config fetch — the resolved values plus the config version
|
|
142
|
+
* they correspond to. The version comes from the same `/config` response
|
|
143
|
+
* that delivered the data, so the SDK can persist it without a second
|
|
144
|
+
* `/version` round trip.
|
|
145
|
+
*/
|
|
146
|
+
export interface FetchResult {
|
|
147
|
+
data: Record<string, unknown>;
|
|
148
|
+
/** Config version from the response, or null if the response omitted it. */
|
|
149
|
+
version: string | null;
|
|
150
|
+
}
|
|
140
151
|
export interface ConfigFetcher {
|
|
141
152
|
/** Fetch all configs for the bound project+environment */
|
|
142
|
-
fetchAll(): Promise<
|
|
153
|
+
fetchAll(): Promise<FetchResult>;
|
|
143
154
|
/** Fetch specific keys only */
|
|
144
|
-
fetchKeys(keys: string[]): Promise<
|
|
155
|
+
fetchKeys(keys: string[]): Promise<FetchResult>;
|
|
145
156
|
/** Check current config version (lightweight — single Firestore read) */
|
|
146
157
|
checkVersion(): Promise<{
|
|
147
158
|
version: string;
|