Skip to content

Commit 3cdbd91

Browse files
authored
chore: update client spec tests to 5.2.2 (#800)
1 parent 441ce11 commit 3cdbd91

File tree

8 files changed

+71
-55
lines changed

8 files changed

+71
-55
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"@types/node": "^20.17.17",
5959
"@types/proxy-from-env": "^1.0.4",
6060
"@types/semver": "^7.5.0",
61-
"@unleash/client-specification": "5.1.9",
61+
"@unleash/client-specification": "5.2.2",
6262
"@vitest/coverage-v8": "4.0.14",
6363
"@vitest/ui": "4.0.14",
6464
"del-cli": "^7.0.0",

src/feature.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export interface EnhancedFeatureInterface extends Omit<FeatureInterface, 'strate
2525
strategies?: EnhancedStrategyTransportInterface[];
2626
}
2727

28+
export type ApiResponse = ClientFeaturesDelta | ClientFeaturesResponse;
29+
2830
export interface ClientFeaturesResponse {
2931
version: number;
3032
features: FeatureInterface[];

src/repository/fetcher.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { EventEmitter } from 'node:events';
2-
import type { ClientFeaturesDelta, ClientFeaturesResponse } from '../feature';
2+
import type { ApiResponse } from '../feature';
33
import type { CustomHeaders, CustomHeadersFunction } from '../headers';
44
import type { HttpOptions } from '../http-options';
55
import type { TagFilter } from '../tags';
@@ -18,8 +18,7 @@ export interface CommonFetchingOptions {
1818
instanceId: string;
1919
headers?: CustomHeaders;
2020
connectionId: string;
21-
onSave: (response: ClientFeaturesResponse, fromApi: boolean) => Promise<void>;
22-
onSaveDelta: (delta: ClientFeaturesDelta) => Promise<void>;
21+
onSave: (response: ApiResponse, fromApi: boolean) => Promise<void>;
2322
onModeChange?: (mode: Mode['type']) => Promise<void>;
2423
}
2524

src/repository/index.ts

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { EventEmitter } from 'node:events';
22
import { UnleashEvents } from '../events';
33
import type {
4-
ClientFeaturesDelta,
4+
ApiResponse,
55
ClientFeaturesResponse,
66
EnhancedFeatureInterface,
77
FeatureInterface,
@@ -134,7 +134,6 @@ export default class Repository extends EventEmitter implements EventEmitter {
134134
mode,
135135
eventSource,
136136
onSave: this.save.bind(this),
137-
onSaveDelta: this.saveDelta.bind(this),
138137
});
139138

140139
this.setupFetchingStrategyEvents();
@@ -207,52 +206,74 @@ export default class Repository extends EventEmitter implements EventEmitter {
207206
return new Map(segments.map((segment) => [segment.id, segment]));
208207
}
209208

210-
public async save(response: ClientFeaturesResponse, fromApi: boolean): Promise<void> {
209+
public async save(response: ApiResponse, fromApi: boolean): Promise<void> {
211210
if (this.stopped) {
212211
return;
213212
}
214213
if (fromApi) {
215214
this.connected = true;
216-
this.data = this.convertToMap(response.features);
217-
this.segments = this.createSegmentLookup(response.segments);
215+
this.applyFeatureResponse(response);
218216
} else if (!this.connected) {
219217
// Only allow bootstrap if not connected
220-
this.data = this.convertToMap(response.features);
221-
this.segments = this.createSegmentLookup(response.segments);
218+
this.applyFeatureResponse(response);
222219
}
223220

224221
this.setReady();
225-
this.emit(UnleashEvents.Changed, [...response.features]);
226-
await this.storageProvider.set(this.appName, response);
222+
const newFeatures = Object.values(this.data);
223+
this.emit(UnleashEvents.Changed, newFeatures);
224+
225+
const clientFeatureResponse: ClientFeaturesResponse = {
226+
version: 'version' in response ? response.version : 2,
227+
features: newFeatures,
228+
segments: [...this.segments.values()],
229+
};
230+
231+
await this.storageProvider.set(this.appName, clientFeatureResponse);
227232
}
228233

229-
public async saveDelta(delta: ClientFeaturesDelta): Promise<void> {
230-
if (this.stopped) {
231-
return;
234+
private applyFeatureResponse(response: ApiResponse): void {
235+
if ('events' in response) {
236+
response.events.forEach((event) => {
237+
switch (event.type) {
238+
case 'feature-updated': {
239+
this.data[event.feature.name] = event.feature;
240+
break;
241+
}
242+
case 'feature-removed': {
243+
delete this.data[event.featureName];
244+
break;
245+
}
246+
case 'segment-updated': {
247+
this.segments.set(event.segment.id, event.segment);
248+
break;
249+
}
250+
case 'segment-removed': {
251+
this.segments.delete(event.segmentId);
252+
break;
253+
}
254+
case 'hydration': {
255+
this.data = this.convertToMap(event.features);
256+
this.segments = this.createSegmentLookup(event.segments);
257+
break;
258+
}
259+
default: {
260+
this.emit(
261+
UnleashEvents.Warn,
262+
`Unknown event type received, this may or may not cause features to evaluate incorrectly: ${JSON.stringify(event)}`,
263+
);
264+
break;
265+
}
266+
}
267+
});
268+
} else if ('features' in response) {
269+
this.data = this.convertToMap(response.features);
270+
this.segments = this.createSegmentLookup(response.segments);
271+
} else {
272+
this.emit(
273+
UnleashEvents.Warn,
274+
`Unknown response when applying feature response: ${JSON.stringify(response)}`,
275+
);
232276
}
233-
this.connected = true;
234-
delta.events.forEach((event) => {
235-
if (event.type === 'feature-updated') {
236-
this.data[event.feature.name] = event.feature;
237-
} else if (event.type === 'feature-removed') {
238-
delete this.data[event.featureName];
239-
} else if (event.type === 'segment-updated') {
240-
this.segments.set(event.segment.id, event.segment);
241-
} else if (event.type === 'segment-removed') {
242-
this.segments.delete(event.segmentId);
243-
} else if (event.type === 'hydration') {
244-
this.data = this.convertToMap(event.features);
245-
this.segments = this.createSegmentLookup(event.segments);
246-
}
247-
});
248-
249-
this.setReady();
250-
this.emit(UnleashEvents.Changed, Object.values(this.data));
251-
await this.storageProvider.set(this.appName, {
252-
features: Object.values(this.data),
253-
segments: [...this.segments.values()],
254-
version: 0,
255-
});
256277
}
257278

258279
notEmpty(content: ClientFeaturesResponse): boolean {

src/repository/polling-fetcher.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { EventEmitter } from 'node:events';
22
import { UnleashEvents } from '../events';
3-
import { parseClientFeaturesDelta } from '../feature';
43
import { get } from '../request';
54
import type { TagFilter } from '../tags';
65
import getUrl from '../url-utils';
@@ -166,12 +165,7 @@ export class PollingFetcher extends EventEmitter implements FetcherInterface {
166165
await this.options.onModeChange('streaming');
167166
return;
168167
}
169-
170-
if (this.options.mode.type === 'polling' && this.options.mode.format === 'delta') {
171-
await this.options.onSaveDelta(parseClientFeaturesDelta(data));
172-
} else {
173-
await this.options.onSave(data, true);
174-
}
168+
await this.options.onSave(data, true);
175169
} catch (err) {
176170
this.emit(UnleashEvents.Error, err);
177171
}

src/repository/streaming-fetcher.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class StreamingFetcher extends EventEmitter implements FetcherInterface {
2020

2121
private readonly connectionId?: string;
2222

23-
private readonly onSaveDelta: StreamingFetchingOptions['onSaveDelta'];
23+
private readonly onSave: StreamingFetchingOptions['onSave'];
2424

2525
private readonly onModeChange?: StreamingFetchingOptions['onModeChange'];
2626

@@ -35,7 +35,7 @@ export class StreamingFetcher extends EventEmitter implements FetcherInterface {
3535
eventSource,
3636
maxFailuresUntilFailover = 5,
3737
failureWindowMs = 60_000,
38-
onSaveDelta,
38+
onSave,
3939
onModeChange,
4040
}: StreamingFetchingOptions) {
4141
super();
@@ -45,7 +45,7 @@ export class StreamingFetcher extends EventEmitter implements FetcherInterface {
4545
this.instanceId = instanceId;
4646
this.headers = headers;
4747
this.connectionId = connectionId;
48-
this.onSaveDelta = onSaveDelta;
48+
this.onSave = onSave;
4949
this.onModeChange = onModeChange;
5050

5151
this.eventSource = eventSource;
@@ -128,7 +128,7 @@ export class StreamingFetcher extends EventEmitter implements FetcherInterface {
128128
private async handleFlagsFromStream(event: { data: string }) {
129129
try {
130130
const data = parseClientFeaturesDelta(JSON.parse(event.data));
131-
await this.onSaveDelta(data);
131+
await this.onSave(data, true);
132132
} catch (err) {
133133
const errorMessage =
134134
err instanceof Error && typeof err.message === 'string' ? err.message : String(err);

src/test/repository/repository.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2074,7 +2074,7 @@ test('SSE with HTTP mocking - should process unleash-updated event', async () =>
20742074
repo.stop();
20752075
});
20762076

2077-
test('SSE parse error forces full rehydration without Last-Event-ID', async (t) => {
2077+
test('SSE parse error forces full rehydration without Last-Event-ID', async () => {
20782078
const url = 'http://unleash-test-sse-parse-error.app';
20792079

20802080
const initialHydration = createSSEResponse([

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -676,10 +676,10 @@
676676
dependencies:
677677
"@types/node" "*"
678678

679-
"@unleash/client-specification@5.1.9":
680-
version "5.1.9"
681-
resolved "https://registry.yarnpkg.com/@unleash/client-specification/-/client-specification-5.1.9.tgz#29e3721e2ead91fafb96f7d4a2d3a51e09a4a4f5"
682-
integrity sha512-9/Z5Kc3uuSe/3cqTCa0P3nWqv6g5ZpD2ZRi6G3blU1C7SAqs6Zixo2etSz9U9tOHSQdDq39X4SM9KQwDutAdYg==
679+
"@unleash/client-specification@5.2.2":
680+
version "5.2.2"
681+
resolved "https://registry.yarnpkg.com/@unleash/client-specification/-/client-specification-5.2.2.tgz#a40601b22f5ceff745ce3871a3317dd83de59729"
682+
integrity sha512-0BDc/K9MHMUFaZti9w0niUuOH+plg5lWVj0yd3KK6WppEmvFwDdHMP8ND36Vyjn22ABraYYftDu7MVvgavSUIw==
683683

684684
"@vitest/coverage-v8@4.0.14":
685685
version "4.0.14"

0 commit comments

Comments
 (0)