Skip to content

Commit 4143ece

Browse files
committed
Restrict Matcher class usage
1 parent 8d8f4e7 commit 4143ece

File tree

7 files changed

+710
-752
lines changed

7 files changed

+710
-752
lines changed

apps/meteor/client/lib/cachedCollections/Cursor.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { createComparatorFromSort, type Filter, type Sort } from '@rocket.chat/mongo-adapter';
1+
import { createComparatorFromSort, createPredicateFromFilter, type Filter, type Sort } from '@rocket.chat/mongo-adapter';
22
import { Tracker } from 'meteor/tracker';
33

44
import { DiffSequence } from './DiffSequence';
55
import { IdMap } from './IdMap';
66
import type { LocalCollection } from './LocalCollection';
7-
import { Matcher } from './Matcher';
87
import { MinimongoError } from './MinimongoError';
98
import { ObserveHandle, ReactiveObserveHandle } from './ObserveHandle';
109
import { OrderedDict } from './OrderedDict';
@@ -19,7 +18,7 @@ type DispatchTransform<TTransform, T, TProjection> = TTransform extends (...args
1918

2019
/** @deprecated internal use only */
2120
export class Cursor<T extends { _id: string }, TOptions extends Options<T>, TProjection extends T = T> {
22-
private readonly matcher: Matcher<T>;
21+
private readonly predicate: (doc: T) => boolean;
2322

2423
private readonly comparator: ((a: T, b: T) => number) | null;
2524

@@ -40,7 +39,7 @@ export class Cursor<T extends { _id: string }, TOptions extends Options<T>, TPro
4039
selector: Filter<T> | T['_id'],
4140
options?: TOptions,
4241
) {
43-
this.matcher = new Matcher(selector);
42+
this.predicate = createPredicateFromFilter(typeof selector === 'string' ? ({ _id: selector } as Filter<T>) : selector);
4443
this.comparator = options?.sort ? createComparatorFromSort(options.sort) : null;
4544
this.skip = options?.skip ?? 0;
4645
this.limit = options?.limit;
@@ -359,19 +358,19 @@ export class Cursor<T extends { _id: string }, TOptions extends Options<T>, TPro
359358
? {
360359
cursor: this,
361360
dirty: false,
362-
matcher: this.matcher,
363361
ordered,
364362
projectionFn: this._projectionFn,
365363
resultsSnapshot: null,
364+
predicate: this.predicate,
366365
comparator: this.comparator,
367366
}
368367
: {
369368
cursor: this,
370369
dirty: false,
371-
matcher: this.matcher,
372370
ordered,
373371
projectionFn: this._projectionFn,
374372
resultsSnapshot: null,
373+
predicate: this.predicate,
375374
comparator: null,
376375
};
377376

@@ -486,8 +485,7 @@ export class Cursor<T extends { _id: string }, TOptions extends Options<T>, TPro
486485
const results: T[] | IdMap<T['_id'], T> = options.ordered ? [] : new IdMap<T['_id'], T>();
487486

488487
for (const doc of this.collection.store.getState().records) {
489-
const matchResult = this.matcher.documentMatches(doc);
490-
if (matchResult.result) {
488+
if (this.predicate(doc)) {
491489
if (options.ordered) {
492490
(results as T[]).push(doc);
493491
} else {

apps/meteor/client/lib/cachedCollections/LocalCollection.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,15 @@ describe('saveOriginals/retrieveOriginals', () => {
246246
describe('internal error/edge cases', () => {
247247
it('throws on invalid modifier', () => {
248248
collection.insert({ ...docA });
249-
expect(() => collection.update({ _id: 'a' }, { $bad: { foo: 1 } } as any)).toThrow();
249+
expect(() => collection.update({ _id: 'a' }, { $bad: { foo: 1 } })).toThrow();
250250
});
251251

252252
it('throws on forbidden field names', () => {
253253
expect(() => collection.insert({ _id: 'x', $bad: 1 } as any)).toThrow();
254254
});
255+
256+
it('throws on changing _id', () => {
257+
collection.insert({ ...docA });
258+
expect(() => collection.update({ _id: 'a' }, { _id: 'newId' })).toThrow();
259+
});
255260
});

apps/meteor/client/lib/cachedCollections/LocalCollection.ts

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createComparatorFromSort, type Filter } from '@rocket.chat/mongo-adapter';
1+
import { createComparatorFromSort, createPredicateFromFilter, type Filter } from '@rocket.chat/mongo-adapter';
22
import { Meteor } from 'meteor/meteor';
33
import type { CountDocumentsOptions, UpdateFilter } from 'mongodb';
44
import type { StoreApi, UseBoundStore } from 'zustand';
@@ -80,9 +80,7 @@ export class LocalCollection<T extends { _id: string }> {
8080
for (const query of this.queries) {
8181
if (query.dirty) continue;
8282

83-
const matchResult = query.matcher.documentMatches(doc);
84-
85-
if (matchResult.result) {
83+
if (query.predicate(doc)) {
8684
if (query.cursor.skip || query.cursor.limit) {
8785
queriesToRecompute.add(query);
8886
} else {
@@ -109,9 +107,7 @@ export class LocalCollection<T extends { _id: string }> {
109107
for await (const query of this.queries) {
110108
if (query.dirty) continue;
111109

112-
const matchResult = query.matcher.documentMatches(doc);
113-
114-
if (matchResult.result) {
110+
if (query.predicate(doc)) {
115111
if (query.cursor.skip || query.cursor.limit) {
116112
queriesToRecompute.add(query);
117113
} else {
@@ -211,11 +207,11 @@ export class LocalCollection<T extends { _id: string }> {
211207
}
212208

213209
private prepareRemove(selector: Filter<T>) {
214-
const matcher = new Matcher(selector);
210+
const predicate = createPredicateFromFilter(selector);
215211
const remove = new Set<T>();
216212

217213
this._eachPossiblyMatchingDoc(selector, (doc) => {
218-
if (matcher.documentMatches(doc).result) {
214+
if (predicate(doc)) {
219215
remove.add(doc);
220216
}
221217
});
@@ -227,7 +223,7 @@ export class LocalCollection<T extends { _id: string }> {
227223
for (const query of this.queries) {
228224
if (query.dirty) continue;
229225

230-
if (query.matcher.documentMatches(removeDoc).result) {
226+
if (query.predicate(removeDoc)) {
231227
if (query.cursor.skip || query.cursor.limit) {
232228
queriesToRecompute.add(query);
233229
} else {
@@ -689,7 +685,7 @@ export class LocalCollection<T extends { _id: string }> {
689685
if (query.dirty) continue;
690686

691687
if (query.ordered) {
692-
matchedBefore.set(query, query.matcher.documentMatches(doc).result);
688+
matchedBefore.set(query, query.predicate(doc));
693689
} else {
694690
matchedBefore.set(query, query.results.has(doc._id));
695691
}
@@ -712,8 +708,7 @@ export class LocalCollection<T extends { _id: string }> {
712708
for (const query of this.queries) {
713709
if (query.dirty) continue;
714710

715-
const afterMatch = query.matcher.documentMatches(doc);
716-
const after = afterMatch.result;
711+
const after = query.predicate(doc);
717712
const before = matchedBefore.get(query);
718713

719714
if (query.cursor.skip || query.cursor.limit) {
@@ -744,8 +739,7 @@ export class LocalCollection<T extends { _id: string }> {
744739
for await (const query of this.queries) {
745740
if (query.dirty) continue;
746741

747-
const afterMatch = query.matcher.documentMatches(doc);
748-
const after = afterMatch.result;
742+
const after = query.predicate(doc);
749743
const before = matchedBefore.get(query);
750744

751745
if (query.cursor.skip || query.cursor.limit) {
@@ -939,21 +933,18 @@ export class LocalCollection<T extends { _id: string }> {
939933
modifier = clone(modifier);
940934

941935
const isModifier = isOperatorObject(modifier);
942-
const newDoc = isModifier ? clone(doc) : (modifier as T);
943936

944937
if (isModifier) {
945-
for (const operator of Object.keys(modifier)) {
946-
const setOnInsert = options.isInsert && operator === '$setOnInsert';
947-
const modFunc = MODIFIERS[(setOnInsert ? '$set' : operator) as keyof typeof MODIFIERS];
948-
const operand = modifier[operator];
938+
const newDoc = clone(doc);
939+
940+
for (const [operator, operand] of Object.entries(modifier)) {
941+
const modFunc = MODIFIERS[options.isInsert && operator === '$setOnInsert' ? '$set' : (operator as keyof typeof MODIFIERS)];
949942

950943
if (!modFunc) {
951944
throw new MinimongoError(`Invalid modifier specified ${operator}`);
952945
}
953946

954-
for (const keypath of Object.keys(operand)) {
955-
const arg = operand[keypath];
956-
947+
for (const [keypath, arg] of Object.entries(operand)) {
957948
if (keypath === '') {
958949
throw new MinimongoError('An empty update path is not valid.');
959950
}
@@ -981,15 +972,17 @@ export class LocalCollection<T extends { _id: string }> {
981972
`_id: "${newDoc._id}"`,
982973
);
983974
}
984-
} else {
985-
if (doc._id && modifier._id && doc._id !== modifier._id) {
986-
throw new MinimongoError(`The _id field cannot be changed from {_id: "${doc._id}"} to {_id: "${modifier._id}"}`);
987-
}
988975

989-
assertHasValidFieldNames(modifier);
976+
return newDoc;
977+
}
978+
979+
if (doc._id && modifier._id && doc._id !== modifier._id) {
980+
throw new MinimongoError(`The _id field cannot be changed from {_id: "${doc._id}"} to {_id: "${modifier._id}"}`);
990981
}
991982

992-
return newDoc;
983+
assertHasValidFieldNames(modifier);
984+
985+
return modifier as T;
993986
}
994987

995988
private _removeFromResults(query: Query<T>, doc: T) {
@@ -1205,7 +1198,7 @@ const MODIFIERS = {
12051198
throw new MinimongoError('$rename target field invalid', { field });
12061199
}
12071200

1208-
target2[keyparts.pop() as string] = object;
1201+
target2[keyparts.pop() as keyof typeof target2] = object;
12091202
},
12101203
$set(target: Record<string, unknown>, field: string, arg: any) {
12111204
if (target !== Object(target)) {
@@ -1410,9 +1403,9 @@ const MODIFIERS = {
14101403

14111404
let out;
14121405
if (arg != null && typeof arg === 'object' && !Array.isArray(arg)) {
1413-
const matcher = new Matcher(arg);
1406+
const predicate = createPredicateFromFilter(arg);
14141407

1415-
out = toPull.filter((element) => !matcher.documentMatches(element).result);
1408+
out = toPull.filter((element) => !predicate(element));
14161409
} else {
14171410
out = toPull.filter((element) => !_f._equal(element, arg));
14181411
}
@@ -1479,7 +1472,7 @@ function assertIsValidFieldName(key: string) {
14791472
}
14801473

14811474
function findModTarget(
1482-
doc: Record<string, any>,
1475+
doc: Record<string, any> | unknown[],
14831476
keyparts: (number | string)[],
14841477
options: {
14851478
noCreate?: boolean;
@@ -1562,9 +1555,7 @@ function findModTarget(
15621555
}
15631556
}
15641557

1565-
if (last) {
1566-
return doc as { [index: string | number]: any };
1567-
}
1558+
if (last) return doc;
15681559

15691560
doc = doc[keypart as keyof typeof doc];
15701561
}

0 commit comments

Comments
 (0)