Skip to content

Commit 6d83e88

Browse files
mogitaszuperaz
andauthored
fix: proper poll typing with user response (#196)
- Fixes #171 The `PollVote.user` type was missing the `image` field, even though the data was present in WebSocket/API responses. This caused TypeScript to not recognize `user.image` as a valid property. Changes: - Replaced `PollVote` import with `PollVoteResponseData` - Removed `Poll as PollType` import (now using `PollResponseData` directly) - Updated `PollState` type to use `Omit<PollResponseData, ...>` - Removed `@ts-expect-error` directives that are no longer needed Type Improvements: | Before | After | |--------|-------| | `user?: User` (no `image`) | `user?: UserResponse` (has `image`) | | `teams_role:` required | `teams_role?:` optional | None breaking changes found. --------- Co-authored-by: Zita Szupera <[email protected]>
1 parent 889b875 commit 6d83e88

File tree

2 files changed

+13
-32
lines changed

2 files changed

+13
-32
lines changed

packages/feeds-client/src/common/Poll.ts

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { StateStore } from '@stream-io/state-store';
22
import type { FeedsClient } from '../feeds-client';
33
import type {
4-
PollVote,
4+
PollVoteResponseData,
55
QueryPollVotesRequest,
66
PollUpdatedFeedEvent,
77
WSEvent,
@@ -10,7 +10,6 @@ import type {
1010
PollVoteChangedFeedEvent,
1111
PollVoteRemovedFeedEvent,
1212
PollResponseData,
13-
Poll as PollType,
1413
} from '../gen/models';
1514

1615
const isPollUpdatedEvent = (
@@ -34,7 +33,8 @@ const isPollVoteRemovedEvent = (
3433
): e is { type: 'feeds.poll.vote_removed' } & PollVoteRemovedFeedEvent =>
3534
e.type === 'feeds.poll.vote_removed';
3635

37-
export const isVoteAnswer = (vote: PollVote) => !!vote?.answer_text;
36+
export const isVoteAnswer = (vote: PollVoteResponseData) =>
37+
!!vote?.answer_text;
3838

3939
export type PollAnswersQueryParams = QueryPollVotesRequest & {
4040
poll_id: string;
@@ -43,16 +43,16 @@ export type PollAnswersQueryParams = QueryPollVotesRequest & {
4343

4444
type OptionId = string;
4545

46-
export type PollState = Omit<PollType, 'own_votes' | 'id'> & {
46+
export type PollState = Omit<PollResponseData, 'own_votes' | 'id'> & {
4747
last_activity_at: Date;
4848
max_voted_option_ids: OptionId[];
49-
own_votes_by_option_id: Record<OptionId, PollVote>;
50-
own_answer?: PollVote; // each user can have only one answer
49+
own_votes_by_option_id: Record<OptionId, PollVoteResponseData>;
50+
own_answer?: PollVoteResponseData; // each user can have only one answer
5151
};
5252

5353
type PollInitOptions = {
5454
client: FeedsClient;
55-
poll: PollType;
55+
poll: PollResponseData;
5656
};
5757

5858
export class StreamPoll {
@@ -74,8 +74,8 @@ export class StreamPoll {
7474
) => {
7575
const { own_votes, id, ...pollResponseForState } = poll;
7676
const { ownAnswer, ownVotes } = own_votes?.reduce<{
77-
ownVotes: PollVote[];
78-
ownAnswer?: PollVote;
77+
ownVotes: PollVoteResponseData[];
78+
ownAnswer?: PollVoteResponseData;
7979
}>(
8080
(acc, voteOrAnswer) => {
8181
if (isVoteAnswer(voteOrAnswer)) {
@@ -111,7 +111,6 @@ export class StreamPoll {
111111
if (event.poll?.id && event.poll.id !== this.id) return;
112112
if (!isPollUpdatedEvent(event as WSEvent)) return;
113113
const { id, ...pollData } = event.poll;
114-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
115114
this.state.partialNext({
116115
...pollData,
117116
last_activity_at: new Date(event.created_at),
@@ -140,19 +139,14 @@ export class StreamPoll {
140139
let maxVotedOptionIds = currentState.max_voted_option_ids;
141140

142141
if (isOwnVote) {
143-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
144142
if (isVoteAnswer(event.poll_vote)) {
145-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
146143
ownAnswer = event.poll_vote;
147144
} else if (event.poll_vote.option_id) {
148-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
149145
ownVotesByOptionId[event.poll_vote.option_id] = event.poll_vote;
150146
}
151147
}
152148

153-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
154149
if (isVoteAnswer(event.poll_vote)) {
155-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
156150
latestAnswers = [event.poll_vote, ...latestAnswers];
157151
} else {
158152
maxVotedOptionIds = getMaxVotedOptionIds(
@@ -168,7 +162,6 @@ export class StreamPoll {
168162
} = event.poll;
169163
this.state.partialNext({
170164
answers_count,
171-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
172165
latest_votes_by_option,
173166
vote_count,
174167
vote_counts_by_option,
@@ -194,22 +187,18 @@ export class StreamPoll {
194187
let maxVotedOptionIds = currentState.max_voted_option_ids;
195188

196189
if (isOwnVote) {
197-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
198190
if (isVoteAnswer(event.poll_vote)) {
199191
latestAnswers = [
200-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
201192
event.poll_vote,
202193
...latestAnswers.filter((answer) => answer.id !== event.poll_vote.id),
203194
];
204-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
205195
ownAnswer = event.poll_vote;
206196
} else if (event.poll_vote.option_id) {
207197
if (event.poll.enforce_unique_vote) {
208-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
209198
ownVotesByOptionId = { [event.poll_vote.option_id]: event.poll_vote };
210199
} else {
211200
ownVotesByOptionId = Object.entries(ownVotesByOptionId).reduce<
212-
Record<OptionId, PollVote>
201+
Record<OptionId, PollVoteResponseData>
213202
>((acc, [optionId, vote]) => {
214203
if (
215204
optionId !== event.poll_vote.option_id &&
@@ -220,7 +209,6 @@ export class StreamPoll {
220209
acc[optionId] = vote;
221210
return acc;
222211
}, {});
223-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
224212
ownVotesByOptionId[event.poll_vote.option_id] = event.poll_vote;
225213
}
226214

@@ -231,9 +219,7 @@ export class StreamPoll {
231219
event.poll.vote_counts_by_option,
232220
);
233221
}
234-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
235222
} else if (isVoteAnswer(event.poll_vote)) {
236-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
237223
latestAnswers = [event.poll_vote, ...latestAnswers];
238224
} else {
239225
maxVotedOptionIds = getMaxVotedOptionIds(
@@ -249,7 +235,6 @@ export class StreamPoll {
249235
} = event.poll;
250236
this.state.partialNext({
251237
answers_count,
252-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
253238
latest_votes_by_option,
254239
vote_count,
255240
vote_counts_by_option,
@@ -273,7 +258,6 @@ export class StreamPoll {
273258
const ownVotesByOptionId = { ...currentState.own_votes_by_option_id };
274259
let maxVotedOptionIds = currentState.max_voted_option_ids;
275260

276-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
277261
if (isVoteAnswer(event.poll_vote)) {
278262
latestAnswers = latestAnswers.filter(
279263
(answer) => answer.id !== event.poll_vote.id,
@@ -298,7 +282,6 @@ export class StreamPoll {
298282
} = event.poll;
299283
this.state.partialNext({
300284
answers_count,
301-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
302285
latest_votes_by_option,
303286
vote_count,
304287
vote_counts_by_option,
@@ -327,10 +310,10 @@ function getMaxVotedOptionIds(
327310
return winningOptions;
328311
}
329312

330-
function getOwnVotesByOptionId(ownVotes: PollVote[]) {
313+
function getOwnVotesByOptionId(ownVotes: PollVoteResponseData[]) {
331314
return !ownVotes
332-
? ({} satisfies Record<OptionId, PollVote>)
333-
: ownVotes.reduce<Record<OptionId, PollVote>>((acc, vote) => {
315+
? ({} satisfies Record<OptionId, PollVoteResponseData>)
316+
: ownVotes.reduce<Record<OptionId, PollVoteResponseData>>((acc, vote) => {
334317
if (isVoteAnswer(vote) || !vote.option_id) return acc;
335318
acc[vote.option_id] = vote;
336319
return acc;

packages/feeds-client/src/feeds-client/feeds-client.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,9 @@ export class FeedsClient extends FeedsApi {
356356
const pollResponse = activity.poll;
357357
const pollFromCache = this.pollFromState(pollResponse.id);
358358
if (!pollFromCache) {
359-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
360359
const poll = new StreamPoll({ client: this, poll: pollResponse });
361360
this.polls_by_id.set(poll.id, poll);
362361
} else {
363-
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
364362
pollFromCache.reinitializeState(pollResponse);
365363
}
366364
}

0 commit comments

Comments
 (0)