Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 apps/meteor/app/authorization/client/hasPermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const validatePermissions = (
return false;
}

if (!PermissionsCachedStore.watchReady()) {
if (!watch(PermissionsCachedStore.useReady, (state) => state)) {
return false;
}

Expand Down
5 changes: 0 additions & 5 deletions apps/meteor/client/lib/cachedStores/CachedStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type { IDocumentMapStore } from './DocumentMapStore';
import { sdk } from '../../../app/utils/client/lib/SDKClient';
import { isTruthy } from '../../../lib/isTruthy';
import { withDebouncing } from '../../../lib/utils/highOrderFunctions';
import { watch } from '../../meteor/watch';
import { getUserId } from '../user';
import { getConfig } from '../utils/getConfig';

Expand Down Expand Up @@ -360,10 +359,6 @@ export abstract class CachedStore<T extends IRocketChatRecord, U = T> implements

private reconnectionComputation: Tracker.Computation | undefined;

watchReady() {
return watch(this.useReady, (ready) => ready);
}

setReady(ready: boolean) {
this.useReady.setState(ready);
}
Expand Down
21 changes: 10 additions & 11 deletions apps/meteor/client/lib/cachedStores/DocumentMapStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,7 @@ export interface IDocumentMapStore<T extends { _id: string }> {
count(predicate: (record: T) => boolean): number;
}

/**
* Factory function to create a Zustand store that holds a map of documents.
*
* @param options - Optional callbacks to handle invalidation of documents.
* @returns the Zustand store with methods to manage the document map.
*/
export const createDocumentMapStore = <T extends { _id: string }>({
onInvalidate,
onInvalidateAll,
}: {
export interface IDocumentMapStoreHooks<T extends { _id: string }> {
/**
* Callback invoked when a document is stored, updated or deleted.
*
Expand All @@ -167,7 +158,15 @@ export const createDocumentMapStore = <T extends { _id: string }>({
* @deprecated prefer subscribing to the store
*/
onInvalidateAll?: () => void;
} = {}) =>
}

/**
* Factory function to create a Zustand store that holds a map of documents.
*
* @param options - Optional callbacks to handle invalidation of documents.
* @returns the Zustand store with methods to manage the document map.
*/
export const createDocumentMapStore = <T extends { _id: string }>({ onInvalidate, onInvalidateAll }: IDocumentMapStoreHooks<T> = {}) =>
create<IDocumentMapStore<T>>()((set, get) => ({
records: new Map(),
has: (id: T['_id']) => get().records.has(id),
Expand Down
36 changes: 15 additions & 21 deletions apps/meteor/client/meteor/minimongo/MinimongoCollection.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Mongo } from 'meteor/mongo';
import type { StoreApi, UseBoundStore } from 'zustand';

import { LocalCollection } from './LocalCollection';
import type { Query } from './queries';
import { createDocumentMapStore } from '../../lib/cachedStores/DocumentMapStore';
import type { IDocumentMapStore } from '../../lib/cachedStores/DocumentMapStore';

/**
* Implements a minimal version of a MongoDB collection using Zustand for state management.
Expand All @@ -12,15 +13,15 @@ import { createDocumentMapStore } from '../../lib/cachedStores/DocumentMapStore'
export class MinimongoCollection<T extends { _id: string }> extends Mongo.Collection<T> {
private pendingRecomputations = new Set<Query<T>>();

private recomputeAll() {
recomputeAll() {
this.pendingRecomputations.clear();

for (const query of this._collection.queries) {
this._collection.recomputeQuery(query);
}
}

private scheduleRecomputationsFor(docs: T[]) {
scheduleRecomputationsFor(docs: T[]) {
for (const query of this._collection.queries) {
if (this.pendingRecomputations.has(query)) continue;

Expand All @@ -43,31 +44,24 @@ export class MinimongoCollection<T extends { _id: string }> extends Mongo.Collec
});
}

/**
* A Zustand store that holds the records of the collection.
*
* It should be used as a hook in React components to access the collection's records and methods.
*
* Beware mutating the store will **asynchronously** trigger recomputations of all Minimongo
* queries that depend on the changed documents.
*/
readonly use = createDocumentMapStore<T>({
onInvalidateAll: () => {
this.recomputeAll();
},
onInvalidate: (...docs) => {
this.scheduleRecomputationsFor(docs);
},
});

/**
* The internal collection that manages the queries and results.
*
* It overrides the default Mongo.Collection's methods to use Zustand for state management.
*/
protected _collection = new LocalCollection<T>(this.use);

constructor() {
constructor(
/**
* A Zustand store that holds the records of the collection.
*
* It should be used as a hook in React components to access the collection's records and methods.
*
* Beware mutating the store will **asynchronously** trigger recomputations of all Minimongo
* queries that depend on the changed documents.
*/
public readonly use: UseBoundStore<StoreApi<IDocumentMapStore<T>>>,
) {
super(null);
}

Expand Down
13 changes: 12 additions & 1 deletion apps/meteor/client/meteor/overrides/userAndUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,28 @@ import { Tracker } from 'meteor/tracker';

import { userIdStore } from '../../lib/user';
import { Users } from '../../stores/Users';
import { MinimongoCollection } from '../minimongo/MinimongoCollection';
import { watchUser, watchUserId } from '../user';

Tracker.autorun(() => {
const userId = Accounts.connection.userId() ?? undefined;
userIdStore.setState(userId);
});

const collection = new MinimongoCollection(Users.use);

Users.hooks.onInvalidate = (...docs) => {
collection.scheduleRecomputationsFor(docs);
};

Users.hooks.onInvalidateAll = () => {
collection.recomputeAll();
};

Meteor.userId = () => watchUserId() ?? null;

// overwrite Meteor.users collection so records on it don't get erased whenever the client reconnects to websocket
Meteor.user = () => (watchUser() as Meteor.User | undefined) ?? null;

// assertion is needed because IUser has more obligatory fields than Meteor.User
Meteor.users = Users.collection as unknown as typeof Meteor.users;
Meteor.users = collection as unknown as typeof Meteor.users;
11 changes: 5 additions & 6 deletions apps/meteor/client/stores/Users.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { IUser } from '@rocket.chat/core-typings';

import type { IDocumentMapStoreHooks } from '../lib/cachedStores/DocumentMapStore';
import { createDocumentMapStore } from '../lib/cachedStores/DocumentMapStore';
import { createGlobalStore } from '../lib/cachedStores/createGlobalStore';
import { MinimongoCollection } from '../meteor/minimongo/MinimongoCollection';

class UsersCollection extends MinimongoCollection<IUser> {}

const collection = new UsersCollection();
const hooks: IDocumentMapStoreHooks<IUser> = {};

/** @deprecated prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */
export const Users = createGlobalStore(collection.use, {
collection,
export const Users = createGlobalStore(createDocumentMapStore<IUser>(hooks), {
hooks,
});
Loading