Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: threads 2.0 #1330

Merged
merged 42 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
7192054
Initial commit
arnautov-anton May 15, 2024
6c51d69
First MVP
arnautov-anton May 24, 2024
23bf70a
Adjustments to state handlers
arnautov-anton May 28, 2024
c2350c2
Add loadUnreadThreads and adjust logic
arnautov-anton Jun 10, 2024
501b8c1
New handlers and extending storage options
arnautov-anton Jun 15, 2024
f5b272f
New handlers and fixes to existing ones
arnautov-anton Jun 20, 2024
2c26384
Upgrade TS, adjust conf and code, add lodash/throttle
arnautov-anton Jun 20, 2024
ab49e93
New handlers and fixes
arnautov-anton Jun 25, 2024
ab16fda
New fixes, handlers and adjustments
arnautov-anton Jul 12, 2024
ba57e0a
New fixes, handlers and adjustments
arnautov-anton Jul 16, 2024
462eca3
Added some ThreadManager tests
arnautov-anton Jul 24, 2024
7b20568
Finalize ThreadManager tests
arnautov-anton Jul 24, 2024
f8a9ea3
Added new tests and fixes
arnautov-anton Jul 25, 2024
69fdaef
Drop lodash.throttle, use own implementation
arnautov-anton Jul 26, 2024
ff29795
Adjust tests and package.json
arnautov-anton Jul 31, 2024
fbc6361
Rename next/previous parameters to "cursors", adjust tests
arnautov-anton Aug 2, 2024
d547870
Upgrade Mocha
arnautov-anton Aug 5, 2024
7590add
Replace unsupported Array.toSpliced
arnautov-anton Aug 5, 2024
0e5d673
Adjust insertion index API
arnautov-anton Aug 6, 2024
bfd5ed5
Remove subscription registration from constructor
arnautov-anton Aug 13, 2024
6c50a9e
chore: thread and thread manager into separate file (#1336)
myandrienko Aug 14, 2024
a08ffb1
feat: do not store channelData in thread state (#1337)
myandrienko Aug 14, 2024
2916a21
fix: updates for state store
myandrienko Aug 21, 2024
20974a8
Adjust ThreadResponse type, remove unused properties, rename generic
arnautov-anton Aug 21, 2024
3d88ef7
fix: typo
myandrienko Aug 21, 2024
928e6a7
One test down
arnautov-anton Aug 21, 2024
e71b7bc
Register TM subscriptions before running tests
arnautov-anton Aug 21, 2024
2ac8fea
WIP
myandrienko Aug 21, 2024
10daa99
Return existingReorderedThreadIds functionality
arnautov-anton Aug 22, 2024
7994292
Remove unnecessary return type
arnautov-anton Aug 23, 2024
3358e12
fix: fix most TODOs and tests for Threads class (#1347)
myandrienko Aug 28, 2024
ffbe784
fix: fix most TODOs and tests for ThreadManager class (#1348)
myandrienko Aug 29, 2024
b63da35
Merge branch 'master' into feat/threads-v2
myandrienko Aug 29, 2024
ac3730f
fix: revert TS bump
myandrienko Aug 29, 2024
ba25d98
fix: revert some unrelated changes
myandrienko Aug 29, 2024
656860a
fix: optimize subscribeManageThreadSubscriptions
myandrienko Aug 29, 2024
a6d69ef
refactor: lazy lookup table
arnautov-anton Aug 29, 2024
082769f
Remove threadData from the Channel instantiation step
arnautov-anton Aug 30, 2024
ea02306
Fix missing initial unreadThreadCount
arnautov-anton Aug 30, 2024
23b70aa
fix: latest replies are always the last page
myandrienko Sep 2, 2024
5e0f99a
Fix tests, add missing TM test
arnautov-anton Sep 2, 2024
226c03d
fix: pagination tests
myandrienko Sep 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ export * from './types';
export * from './segment';
export * from './campaign';
export { isOwnUser, chatCodes, logChatPromiseExecution, formatMessage } from './utils';
export * from './store/SimpleStateStore';
export * from './store';
57 changes: 57 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export type Patch<T> = (value: T) => T;
export type Handler<T> = (nextValue: T, previousValue: T | undefined) => void;
export type Unsubscibe = () => void;

function isPatch<T>(value: T | Patch<T>): value is Patch<T> {
return typeof value === 'function';
}

export class StateStore<T extends Record<string, unknown>> {
private handlerSet = new Set<Handler<T>>();

constructor(private value: T) {}

public next = (newValueOrPatch: T | Patch<T>): void => {
// newValue (or patch output) should never be mutated previous value
const newValue = isPatch(newValueOrPatch) ? newValueOrPatch(this.value) : newValueOrPatch;

// do not notify subscribers if the value hasn't changed
if (newValue === this.value) return;

const oldValue = this.value;
this.value = newValue;

this.handlerSet.forEach((handler) => handler(this.value, oldValue));
};

public partialNext = (partial: Partial<T>): void => this.next((current) => ({ ...current, ...partial }));

public getLatestValue = (): T => this.value;

public subscribe = (handler: Handler<T>): Unsubscibe => {
arnautov-anton marked this conversation as resolved.
Show resolved Hide resolved
handler(this.value, undefined);
this.handlerSet.add(handler);
return () => {
this.handlerSet.delete(handler);
};
};

public subscribeWithSelector = <O extends readonly unknown[]>(selector: (nextValue: T) => O, handler: Handler<O>) => {
// begin with undefined to reduce amount of selector calls
let selectedValues: O | undefined;

const wrappedHandler: Handler<T> = (nextValue) => {
const newlySelectedValues = selector(nextValue);
const hasUpdatedValues = selectedValues?.some((value, index) => value !== newlySelectedValues[index]) ?? true;

if (!hasUpdatedValues) return;

const oldSelectedValues = selectedValues;
selectedValues = newlySelectedValues;

handler(newlySelectedValues, oldSelectedValues);
};

return this.subscribe(wrappedHandler);
};
}
84 changes: 0 additions & 84 deletions src/store/SimpleStateStore.ts

This file was deleted.

20 changes: 10 additions & 10 deletions src/thread.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Channel } from './channel';
import type { StreamChat } from './client';
import { SimpleStateStore } from './store/SimpleStateStore';
import { StateStore } from './store';
import type {
AscDesc,
DefaultGenerics,
Expand Down Expand Up @@ -81,7 +81,7 @@ export const DEFAULT_MARK_AS_READ_THROTTLE_DURATION = 1000;
// TODO: store users someplace and reference them from state as now replies might contain users with stale information

export class Thread<Scg extends ExtendableGenerics = DefaultGenerics> {
public readonly state: SimpleStateStore<ThreadState<Scg>>;
public readonly state: StateStore<ThreadState<Scg>>;
public id: string;

private client: StreamChat<Scg>;
Expand All @@ -100,7 +100,7 @@ export class Thread<Scg extends ExtendableGenerics = DefaultGenerics> {

const placeholderDate = new Date();

this.state = new SimpleStateStore<ThreadState<Scg>>({
this.state = new StateStore<ThreadState<Scg>>({
// used as handler helper - actively mark read all of the incoming messages
// if the thread is active (visibly selected in the UI or main focal point in advanced list)
active: false,
Expand Down Expand Up @@ -142,11 +142,11 @@ export class Thread<Scg extends ExtendableGenerics = DefaultGenerics> {
}

public activate = () => {
this.state.patchedNext('active', true);
this.state.partialNext({ active: true });
};

public deactivate = () => {
this.state.patchedNext('active', false);
this.state.partialNext({ active: false });
};

// take state of one instance and merge it to the current instance
Expand Down Expand Up @@ -265,7 +265,7 @@ export class Thread<Scg extends ExtendableGenerics = DefaultGenerics> {

if (event.channel.cid !== channel.cid) return;

this.state.patchedNext('isStateStale', true);
this.state.partialNext({ isStateStale: true });
}).unsubscribe,
);

Expand Down Expand Up @@ -476,7 +476,7 @@ export class Thread<Scg extends ExtendableGenerics = DefaultGenerics> {
sort,
limit = DEFAULT_PAGE_LIMIT,
}: Pick<QueryRepliesOptions<Scg>, 'sort' | 'limit'> = {}) => {
this.state.patchedNext('loadingNextPage', true);
this.state.partialNext({ loadingNextPage: true });

const { loadingNextPage, nextCursor } = this.state.getLatestValue();

Expand All @@ -502,7 +502,7 @@ export class Thread<Scg extends ExtendableGenerics = DefaultGenerics> {
} catch (error) {
this.client.logger('error', (error as Error).message);
} finally {
this.state.patchedNext('loadingNextPage', false);
this.state.partialNext({ loadingNextPage: false });
}
};

Expand All @@ -514,7 +514,7 @@ export class Thread<Scg extends ExtendableGenerics = DefaultGenerics> {

if (loadingPreviousPage || previousCursor === null) return;

this.state.patchedNext('loadingPreviousPage', true);
this.state.partialNext({ loadingPreviousPage: true });

try {
const data = await this.queryReplies({
Expand All @@ -535,7 +535,7 @@ export class Thread<Scg extends ExtendableGenerics = DefaultGenerics> {
} catch (error) {
this.client.logger('error', (error as Error).message);
} finally {
this.state.patchedNext('loadingPreviousPage', false);
this.state.partialNext({ loadingPreviousPage: false });
}
};
}
16 changes: 8 additions & 8 deletions src/thread_manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { StreamChat } from './client';
import type { Handler } from './store/SimpleStateStore';
import { SimpleStateStore } from './store/SimpleStateStore';
import type { Handler } from './store';
import { StateStore } from './store';
import type { Thread } from './thread';
import type { DefaultGenerics, Event, ExtendableGenerics, QueryThreadsOptions } from './types';
import { throttle } from './utils';
Expand All @@ -25,13 +25,13 @@ export type ThreadManagerState<Scg extends ExtendableGenerics = DefaultGenerics>
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };

export class ThreadManager<Scg extends ExtendableGenerics = DefaultGenerics> {
public readonly state: SimpleStateStore<ThreadManagerState<Scg>>;
public readonly state: StateStore<ThreadManagerState<Scg>>;
private client: StreamChat<Scg>;
private unsubscribeFunctions: Set<() => void> = new Set();

constructor({ client }: { client: StreamChat<Scg> }) {
this.client = client;
this.state = new SimpleStateStore<ThreadManagerState<Scg>>({
this.state = new StateStore<ThreadManagerState<Scg>>({
active: false,
threads: [],
threadIdIndexMap: {},
Expand All @@ -46,11 +46,11 @@ export class ThreadManager<Scg extends ExtendableGenerics = DefaultGenerics> {
}

public activate = () => {
this.state.patchedNext('active', true);
this.state.partialNext({ active: true });
};

public deactivate = () => {
this.state.patchedNext('active', false);
this.state.partialNext({ active: false });
};

// eslint-disable-next-line sonarjs/cognitive-complexity
Expand Down Expand Up @@ -96,7 +96,7 @@ export class ThreadManager<Scg extends ExtendableGenerics = DefaultGenerics> {
try {
// FIXME: syncing does not work for me
await this.client.sync(Array.from(channelCids), lastConnectionDownAt.toISOString(), { watch: true });
this.state.patchedNext('lastConnectionDownAt', null);
this.state.partialNext({ lastConnectionDownAt: null });
} catch (error) {
// TODO: if error mentions that the amount of events is more than 2k
// do a reload-type recovery (re-query threads and merge states)
Expand All @@ -122,7 +122,7 @@ export class ThreadManager<Scg extends ExtendableGenerics = DefaultGenerics> {
const { lastConnectionDownAt } = this.state.getLatestValue();

if (!event.online && !lastConnectionDownAt) {
this.state.patchedNext('lastConnectionDownAt', new Date());
this.state.partialNext({ lastConnectionDownAt: new Date() });
}
}).unsubscribe,
);
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,6 @@ export type ThreadResponse<StreamChatGenerics extends ExtendableGenerics = Defau
channel: ChannelResponse<StreamChatGenerics>;
channel_cid: string;
created_at: string;
deleted_at: string;
latest_replies: MessageResponse<StreamChatGenerics>[];
parent_message_id: string;
reply_count: number;
Expand All @@ -513,6 +512,7 @@ export type ThreadResponse<StreamChatGenerics extends ExtendableGenerics = Defau
}[];
title: string;
updated_at: string;
deleted_at?: string;
parent_message?: MessageResponse<StreamChatGenerics>;
read?: {
last_read: string;
Expand Down
Loading
Loading