Skip to content

Commit 47ec2d1

Browse files
committed
refactor: extract out client + improve doc comments
1 parent 6221c67 commit 47ec2d1

5 files changed

Lines changed: 207 additions & 107 deletions

File tree

src/evaluate.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,24 @@ import {
1010
type HashByPropertyValue,
1111
type ProjectEnvId,
1212
} from "./types";
13-
import type { MinimumConfig, Resolver } from "./resolver";
13+
import type { MinimumConfig } from "./resolver";
1414

1515
import { type GetValue, unwrap } from "./unwrap";
1616
import { contextLookup } from "./contextLookup";
1717
import { sortRows } from "./sortRows";
1818
import SemanticVersion from "./semanticversion";
1919
import { isBigInt, jsonStringifyWithBigInt } from "./bigIntUtils";
2020

21+
/**
22+
* Minimal interface for resolving segments and encryption keys during evaluation.
23+
* This interface is used internally to decouple evaluate.ts from the full Resolver.
24+
* @internal
25+
*/
26+
interface SegmentResolver {
27+
raw(key: string): MinimumConfig | undefined;
28+
get(key: string, contexts?: Contexts): unknown;
29+
}
30+
2131
const getHashByPropertyValue = (
2232
value: ConfigValue | undefined,
2333
contexts: Contexts
@@ -102,7 +112,7 @@ const propContainsOneOf = (
102112
const inSegment = (
103113
criterion: Criterion,
104114
contexts: Contexts,
105-
resolver: Resolver
115+
resolver: SegmentResolver
106116
): boolean => {
107117
const segmentKey = criterion.valueToMatch?.string;
108118

@@ -282,7 +292,7 @@ const allCriteriaMatch = (
282292
value: ConditionalValue,
283293
namespace: string | undefined,
284294
contexts: Contexts,
285-
resolver: Resolver
295+
resolver: SegmentResolver
286296
): boolean => {
287297
if (value.criteria === undefined) {
288298
return true;
@@ -380,7 +390,7 @@ const matchingConfigValue = (
380390
projectEnvId: ProjectEnvId,
381391
namespace: string | undefined,
382392
contexts: Contexts,
383-
resolver: Resolver
393+
resolver: SegmentResolver
384394
): [number, number, ConfigValue | undefined] => {
385395
let match: ConfigValue | undefined;
386396
let conditionalValueIndex: number = -1;
@@ -413,7 +423,7 @@ export interface EvaluateArgs {
413423
projectEnvId: ProjectEnvId;
414424
namespace: string | undefined;
415425
contexts: Contexts;
416-
resolver: Resolver;
426+
resolver: SegmentResolver;
417427
}
418428

419429
export interface Evaluation {

src/reforge.ts

Lines changed: 1 addition & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { loadConfig } from "./loadConfig";
33
import { Resolver, type MinimumConfig } from "./resolver";
44
import { Sources } from "./sources";
55
import { jsonStringifyWithBigInt } from "./bigIntUtils";
6-
import { mergeContexts, contextObjToMap } from "./mergeContexts";
76
import {
87
ConfigType,
98
ConfigValueType,
@@ -26,6 +25,7 @@ import type {
2625
import { LOG_LEVEL_RANK_LOOKUP, type makeLogger } from "./logger";
2726
import { SSEConnection } from "./sseConnection";
2827
import { TelemetryReporter } from "./telemetry/reporter";
28+
import { ReforgeClient } from "./reforgeClient";
2929

3030
import type { ContextUploadMode } from "./telemetry/types";
3131
import { knownLoggers } from "./telemetry/knownLoggers";
@@ -135,103 +135,6 @@ interface ConstructorProps {
135135
onUpdate?: (configs: Array<Config | MinimumConfig>) => void;
136136
}
137137

138-
/**
139-
* A context-scoped Reforge client that shares the underlying resolver
140-
* with its parent Reforge instance but applies a specific context to all operations.
141-
*/
142-
class ReforgeClient implements ReforgeInterface {
143-
private readonly parent: Reforge;
144-
private readonly context: Contexts;
145-
146-
constructor(parent: Reforge, contexts: Contexts | ContextObj) {
147-
this.parent = parent;
148-
this.context =
149-
contexts instanceof Map ? contexts : contextObjToMap(contexts);
150-
}
151-
152-
get telemetry(): Telemetry | undefined {
153-
return this.parent.telemetry;
154-
}
155-
156-
get<K extends keyof TypedNodeServerConfigurationRaw>(
157-
key: K,
158-
contexts?: Contexts | ContextObj,
159-
defaultValue?: TypedNodeServerConfigurationRaw[K]
160-
): TypedNodeServerConfigurationRaw[K] {
161-
const mergedContexts = contexts
162-
? mergeContexts(this.context, contexts)
163-
: this.context;
164-
return this.parent.get(key, mergedContexts, defaultValue);
165-
}
166-
167-
isFeatureEnabled(
168-
key: string,
169-
contexts?: Contexts | ContextObj
170-
): boolean {
171-
const mergedContexts = contexts
172-
? mergeContexts(this.context, contexts)
173-
: this.context;
174-
return this.parent.isFeatureEnabled(key, mergedContexts);
175-
}
176-
177-
logger(
178-
loggerName: string,
179-
defaultLevel?: LogLevel,
180-
contexts?: Contexts | ContextObj
181-
): ReturnType<typeof makeLogger> {
182-
const mergedContexts = contexts
183-
? mergeContexts(this.context, contexts)
184-
: this.context;
185-
return this.parent.logger(loggerName, defaultLevel, mergedContexts);
186-
}
187-
188-
shouldLog({
189-
loggerName,
190-
desiredLevel,
191-
defaultLevel,
192-
contexts,
193-
}: {
194-
loggerName: string;
195-
desiredLevel: LogLevel;
196-
defaultLevel?: LogLevel;
197-
contexts?: Contexts | ContextObj;
198-
}): boolean {
199-
const mergedContexts = contexts
200-
? mergeContexts(this.context, contexts)
201-
: this.context;
202-
return this.parent.shouldLog({
203-
loggerName,
204-
desiredLevel,
205-
defaultLevel,
206-
contexts: mergedContexts,
207-
});
208-
}
209-
210-
getLogLevel(loggerName: string): LogLevel {
211-
return this.parent.getLogLevel(loggerName);
212-
}
213-
214-
updateIfStalerThan(durationInMs: number): Promise<void> | undefined {
215-
return this.parent.updateIfStalerThan(durationInMs);
216-
}
217-
218-
withContext(contexts: Contexts | ContextObj): ReforgeInterface {
219-
const mergedContexts = mergeContexts(this.context, contexts);
220-
return new ReforgeClient(this.parent, mergedContexts);
221-
}
222-
223-
inContext<T>(
224-
contexts: Contexts | ContextObj,
225-
func: (reforge: ReforgeInterface) => T
226-
): T {
227-
const mergedContexts = mergeContexts(this.context, contexts);
228-
return func(new ReforgeClient(this.parent, mergedContexts));
229-
}
230-
231-
addConfigChangeListener(callback: GlobalListenerCallback): () => void {
232-
return this.parent.addConfigChangeListener(callback);
233-
}
234-
}
235138

236139
class Reforge implements ReforgeInterface {
237140
private readonly sdkKey: string;

src/reforgeClient.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { mergeContexts } from "./mergeContexts";
2+
import type {
3+
Contexts,
4+
ContextObj,
5+
LogLevel,
6+
} from "./types";
7+
import type { makeLogger } from "./logger";
8+
import type {
9+
ReforgeInterface,
10+
Telemetry,
11+
TypedNodeServerConfigurationRaw,
12+
} from "./reforge";
13+
import type { GlobalListenerCallback } from "./configChangeNotifier";
14+
import { contextObjToMap } from "./mergeContexts";
15+
16+
/**
17+
* Interface for the parent Reforge instance.
18+
* Used internally by ReforgeClient to avoid circular dependencies.
19+
* @internal
20+
*/
21+
interface ReforgeParent {
22+
telemetry: Telemetry | undefined;
23+
get<K extends keyof TypedNodeServerConfigurationRaw>(
24+
key: K,
25+
contexts?: Contexts | ContextObj,
26+
defaultValue?: TypedNodeServerConfigurationRaw[K]
27+
): TypedNodeServerConfigurationRaw[K];
28+
isFeatureEnabled(key: string, contexts?: Contexts | ContextObj): boolean;
29+
logger(
30+
loggerName: string,
31+
defaultLevel?: LogLevel,
32+
contexts?: Contexts | ContextObj
33+
): ReturnType<typeof makeLogger>;
34+
shouldLog(args: {
35+
loggerName: string;
36+
desiredLevel: LogLevel;
37+
defaultLevel?: LogLevel;
38+
contexts?: Contexts | ContextObj;
39+
}): boolean;
40+
getLogLevel(loggerName: string): LogLevel;
41+
updateIfStalerThan(durationInMs: number): Promise<void> | undefined;
42+
addConfigChangeListener(callback: GlobalListenerCallback): () => void;
43+
}
44+
45+
/**
46+
* A context-scoped Reforge client that shares the underlying resolver
47+
* with its parent Reforge instance but applies a specific context to all operations.
48+
*
49+
* This allows creating request/call-scoped clients without duplicating
50+
* the resolver, polling, or SSE connections. All scoped clients share
51+
* the same parent Reforge's state.
52+
*/
53+
export class ReforgeClient implements ReforgeInterface {
54+
private readonly parent: ReforgeParent;
55+
private readonly context: Contexts;
56+
57+
constructor(parent: ReforgeParent, contexts: Contexts | ContextObj) {
58+
this.parent = parent;
59+
this.context =
60+
contexts instanceof Map ? contexts : contextObjToMap(contexts);
61+
}
62+
63+
get telemetry(): Telemetry | undefined {
64+
return this.parent.telemetry;
65+
}
66+
67+
get<K extends keyof TypedNodeServerConfigurationRaw>(
68+
key: K,
69+
contexts?: Contexts | ContextObj,
70+
defaultValue?: TypedNodeServerConfigurationRaw[K]
71+
): TypedNodeServerConfigurationRaw[K] {
72+
const mergedContexts = contexts
73+
? mergeContexts(this.context, contexts)
74+
: this.context;
75+
return this.parent.get(key, mergedContexts, defaultValue);
76+
}
77+
78+
isFeatureEnabled(
79+
key: string,
80+
contexts?: Contexts | ContextObj
81+
): boolean {
82+
const mergedContexts = contexts
83+
? mergeContexts(this.context, contexts)
84+
: this.context;
85+
return this.parent.isFeatureEnabled(key, mergedContexts);
86+
}
87+
88+
logger(
89+
loggerName: string,
90+
defaultLevel?: LogLevel,
91+
contexts?: Contexts | ContextObj
92+
): ReturnType<typeof makeLogger> {
93+
const mergedContexts = contexts
94+
? mergeContexts(this.context, contexts)
95+
: this.context;
96+
return this.parent.logger(loggerName, defaultLevel, mergedContexts);
97+
}
98+
99+
shouldLog({
100+
loggerName,
101+
desiredLevel,
102+
defaultLevel,
103+
contexts,
104+
}: {
105+
loggerName: string;
106+
desiredLevel: LogLevel;
107+
defaultLevel?: LogLevel;
108+
contexts?: Contexts | ContextObj;
109+
}): boolean {
110+
const mergedContexts = contexts
111+
? mergeContexts(this.context, contexts)
112+
: this.context;
113+
return this.parent.shouldLog({
114+
loggerName,
115+
desiredLevel,
116+
defaultLevel,
117+
contexts: mergedContexts,
118+
});
119+
}
120+
121+
getLogLevel(loggerName: string): LogLevel {
122+
return this.parent.getLogLevel(loggerName);
123+
}
124+
125+
updateIfStalerThan(durationInMs: number): Promise<void> | undefined {
126+
return this.parent.updateIfStalerThan(durationInMs);
127+
}
128+
129+
withContext(contexts: Contexts | ContextObj): ReforgeInterface {
130+
const mergedContexts = mergeContexts(this.context, contexts);
131+
return new ReforgeClient(this.parent, mergedContexts);
132+
}
133+
134+
inContext<T>(
135+
contexts: Contexts | ContextObj,
136+
func: (reforge: ReforgeInterface) => T
137+
): T {
138+
const mergedContexts = mergeContexts(this.context, contexts);
139+
return func(new ReforgeClient(this.parent, mergedContexts));
140+
}
141+
142+
addConfigChangeListener(callback: GlobalListenerCallback): () => void {
143+
return this.parent.addConfigChangeListener(callback);
144+
}
145+
}

0 commit comments

Comments
 (0)