diff --git a/apps/meteor/app/authorization/client/hasPermission.ts b/apps/meteor/app/authorization/client/hasPermission.ts index 7dd5e03413475..dd48ce48216c2 100644 --- a/apps/meteor/app/authorization/client/hasPermission.ts +++ b/apps/meteor/app/authorization/client/hasPermission.ts @@ -56,7 +56,7 @@ const validatePermissions = ( return false; } - if (!PermissionsCachedStore.watchReady()) { + if (!watch(PermissionsCachedStore.useReady, (state) => state)) { return false; } diff --git a/apps/meteor/client/lib/cachedStores/CachedStore.ts b/apps/meteor/client/lib/cachedStores/CachedStore.ts index 63ee34a4d5ddc..8918c5663aea3 100644 --- a/apps/meteor/client/lib/cachedStores/CachedStore.ts +++ b/apps/meteor/client/lib/cachedStores/CachedStore.ts @@ -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'; @@ -360,10 +359,6 @@ export abstract class CachedStore implements private reconnectionComputation: Tracker.Computation | undefined; - watchReady() { - return watch(this.useReady, (ready) => ready); - } - setReady(ready: boolean) { this.useReady.setState(ready); } diff --git a/apps/meteor/client/lib/cachedStores/DocumentMapStore.ts b/apps/meteor/client/lib/cachedStores/DocumentMapStore.ts index b52df250acc78..551fa25915e52 100644 --- a/apps/meteor/client/lib/cachedStores/DocumentMapStore.ts +++ b/apps/meteor/client/lib/cachedStores/DocumentMapStore.ts @@ -143,16 +143,7 @@ export interface IDocumentMapStore { 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 = ({ - onInvalidate, - onInvalidateAll, -}: { +export interface IDocumentMapStoreHooks { /** * Callback invoked when a document is stored, updated or deleted. * @@ -167,7 +158,15 @@ export const createDocumentMapStore = ({ * @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 = ({ onInvalidate, onInvalidateAll }: IDocumentMapStoreHooks = {}) => create>()((set, get) => ({ records: new Map(), has: (id: T['_id']) => get().records.has(id), diff --git a/apps/meteor/client/meteor/minimongo/MinimongoCollection.ts b/apps/meteor/client/meteor/minimongo/MinimongoCollection.ts index b4c4346722b41..73270b3cb8715 100644 --- a/apps/meteor/client/meteor/minimongo/MinimongoCollection.ts +++ b/apps/meteor/client/meteor/minimongo/MinimongoCollection.ts @@ -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. @@ -12,7 +13,7 @@ import { createDocumentMapStore } from '../../lib/cachedStores/DocumentMapStore' export class MinimongoCollection extends Mongo.Collection { private pendingRecomputations = new Set>(); - private recomputeAll() { + recomputeAll() { this.pendingRecomputations.clear(); for (const query of this._collection.queries) { @@ -20,7 +21,7 @@ export class MinimongoCollection extends Mongo.Collec } } - private scheduleRecomputationsFor(docs: T[]) { + scheduleRecomputationsFor(docs: T[]) { for (const query of this._collection.queries) { if (this.pendingRecomputations.has(query)) continue; @@ -43,23 +44,6 @@ export class MinimongoCollection 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({ - onInvalidateAll: () => { - this.recomputeAll(); - }, - onInvalidate: (...docs) => { - this.scheduleRecomputationsFor(docs); - }, - }); - /** * The internal collection that manages the queries and results. * @@ -67,7 +51,17 @@ export class MinimongoCollection extends Mongo.Collec */ protected _collection = new LocalCollection(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>>, + ) { super(null); } diff --git a/apps/meteor/client/meteor/overrides/userAndUsers.ts b/apps/meteor/client/meteor/overrides/userAndUsers.ts index f603a9aaaf8a8..aa240aeb00042 100644 --- a/apps/meteor/client/meteor/overrides/userAndUsers.ts +++ b/apps/meteor/client/meteor/overrides/userAndUsers.ts @@ -4,6 +4,7 @@ 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(() => { @@ -11,10 +12,20 @@ Tracker.autorun(() => { 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; diff --git a/apps/meteor/client/stores/Users.ts b/apps/meteor/client/stores/Users.ts index 96fb690b61110..3cae07db89d66 100644 --- a/apps/meteor/client/stores/Users.ts +++ b/apps/meteor/client/stores/Users.ts @@ -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 {} - -const collection = new UsersCollection(); +const hooks: IDocumentMapStoreHooks = {}; /** @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(hooks), { + hooks, });