@rebasepro/server-mongo 0.13.0 → 0.13.1-canary.g249daa1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,7 +49,7 @@ export declare class MongoDriver implements DataDriver {
49
49
  /**
50
50
  * Fetch a collection of rows
51
51
  */
52
- fetchCollection<M extends Record<string, any>>({ path, collection, filter, limit, startAfter, orderBy, searchString, order }: FetchCollectionProps<M>): Promise<Record<string, unknown>[]>;
52
+ fetchCollection<M extends Record<string, any>>(props: FetchCollectionProps<M>): Promise<Record<string, unknown>[]>;
53
53
  /**
54
54
  * Listen to collection changes
55
55
  */
@@ -81,7 +81,7 @@ export declare class MongoDriver implements DataDriver {
81
81
  /**
82
82
  * Count rows in a collection
83
83
  */
84
- count<M extends Record<string, any>>({ path, collection, filter }: FetchCollectionProps<M>): Promise<number>;
84
+ count<M extends Record<string, any>>({ path, collection, filter, logical, searchString }: FetchCollectionProps<M>): Promise<number>;
85
85
  /**
86
86
  * Generate a unique subscription ID
87
87
  */
@@ -1 +1 @@
1
- {"version":3,"file":"MongoDriver.d.ts","sourceRoot":"","sources":["../../src/services/MongoDriver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MongoDriver.d.ts","sourceRoot":"","sources":["../../src/services/MongoDriver.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/server-mongo",
3
3
  "type": "module",
4
- "version": "0.13.0",
4
+ "version": "0.13.1-canary.g249daa1",
5
5
  "description": "MongoDB backend for Rebase",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -40,9 +40,9 @@
40
40
  "dependencies": {
41
41
  "mongodb": "^7.5.0",
42
42
  "ws": "^8.21.1",
43
- "@rebasepro/common": "0.13.0",
44
- "@rebasepro/types": "0.13.0",
45
- "@rebasepro/utils": "0.13.0"
43
+ "@rebasepro/types": "0.13.1-canary.g249daa1",
44
+ "@rebasepro/utils": "0.13.1-canary.g249daa1",
45
+ "@rebasepro/common": "0.13.1-canary.g249daa1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/jest": "^30.0.0",
@@ -55,7 +55,7 @@
55
55
  "vite": "^8.1.5"
56
56
  },
57
57
  "peerDependencies": {
58
- "@rebasepro/server": "0.13.0"
58
+ "@rebasepro/server": "0.13.1-canary.g249daa1"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "@rebasepro/server": {
@@ -4,7 +4,7 @@
4
4
  * Translates Rebase filter conditions to MongoDB query operators.
5
5
  */
6
6
 
7
- import { CollectionConfig, FilterValues, WhereFilterOp } from "@rebasepro/types";
7
+ import { CollectionConfig, FilterCondition, FilterValues, LogicalCondition, WhereFilterOp } from "@rebasepro/types";
8
8
  import { Filter, Document } from "mongodb";
9
9
  import { logger } from "@rebasepro/server";
10
10
 
@@ -35,10 +35,22 @@ function escapeRegExp(str: string): string {
35
35
  */
36
36
  function likePatternToRegExp(pattern: string, caseInsensitive: boolean): RegExp {
37
37
  let body = "";
38
+ // Runs of `%` collapse to one. `%%%%X` means exactly what `%X` means, but as
39
+ // a regular expression it is four adjacent unbounded quantifiers, and on a
40
+ // subject that does not match, the engine tries every way of splitting the
41
+ // subject between them — exponential time. The pattern comes from a public
42
+ // filter operator over HTTP (`?title=like.%25%25%25…`), and this expression
43
+ // is handed to MongoDB as `$regex`, so the time is spent on a database
44
+ // thread rather than the caller's.
45
+ let lastWasWildcard = false;
38
46
  for (const ch of String(pattern)) {
39
- if (ch === "%") body += ".*";
40
- else if (ch === "_") body += ".";
41
- else body += escapeRegExp(ch);
47
+ if (ch === "%") {
48
+ if (!lastWasWildcard) body += ".*";
49
+ lastWasWildcard = true;
50
+ continue;
51
+ }
52
+ body += ch === "_" ? "." : escapeRegExp(ch);
53
+ lastWasWildcard = false;
42
54
  }
43
55
  return new RegExp(`^${body}$`, caseInsensitive ? "i" : "");
44
56
  }
@@ -67,48 +79,81 @@ export class MongoConditionBuilder {
67
79
  if (!filterParam) continue;
68
80
 
69
81
  const [op, value] = filterParam as [WhereFilterOp, any];
82
+ const condition = this.buildCondition(field, op, value);
83
+ if (condition) conditions.push(condition);
84
+ }
70
85
 
71
- // Null-testing operators ignore their value.
72
- if (op === "is-null") {
73
- conditions.push({ [field]: { $eq: null } });
74
- continue;
75
- }
76
- if (op === "is-not-null") {
77
- conditions.push({ [field]: { $ne: null } });
78
- continue;
79
- }
86
+ return conditions;
87
+ }
80
88
 
81
- // Pattern matching → regular expressions.
82
- if (op === "like" || op === "ilike" || op === "not-like" || op === "not-ilike") {
83
- const caseInsensitive = op === "ilike" || op === "not-ilike";
84
- const regex = likePatternToRegExp(value, caseInsensitive);
85
- const negated = op === "not-like" || op === "not-ilike";
86
- conditions.push({
87
- [field]: negated ? { $not: regex } : { $regex: regex }
88
- });
89
- continue;
90
- }
89
+ /**
90
+ * One field, one operator, one value.
91
+ *
92
+ * Extracted so that `logical` groups translate through exactly this code.
93
+ * A group written as a second dialect is a group where `array-contains` or
94
+ * `ilike` quietly means something else than it does in `filter`, which is
95
+ * the kind of difference nobody finds until a query returns the wrong rows.
96
+ */
97
+ private static buildCondition(
98
+ field: string,
99
+ op: WhereFilterOp,
100
+ value: any
101
+ ): Filter<Document> | undefined {
102
+ // Null-testing operators ignore their value.
103
+ if (op === "is-null") return { [field]: { $eq: null } };
104
+ if (op === "is-not-null") return { [field]: { $ne: null } };
91
105
 
92
- const mongoOp = REBASE_TO_MONGO_OP[op];
106
+ // Pattern matching → regular expressions.
107
+ if (op === "like" || op === "ilike" || op === "not-like" || op === "not-ilike") {
108
+ const caseInsensitive = op === "ilike" || op === "not-ilike";
109
+ const regex = likePatternToRegExp(value, caseInsensitive);
110
+ const negated = op === "not-like" || op === "not-ilike";
111
+ return { [field]: negated ? { $not: regex } : { $regex: regex } };
112
+ }
93
113
 
94
- if (!mongoOp) {
95
- logger.warn(`Unsupported filter operator: ${op}`);
96
- continue;
97
- }
114
+ const mongoOp = REBASE_TO_MONGO_OP[op];
98
115
 
99
- // Handle array-contains specially
100
- if (op === "array-contains") {
101
- conditions.push({
102
- [field]: { $elemMatch: { $eq: value } }
103
- });
104
- } else {
105
- conditions.push({
106
- [field]: { [mongoOp]: value }
107
- });
116
+ if (!mongoOp) {
117
+ logger.warn(`Unsupported filter operator: ${op}`);
118
+ return undefined;
119
+ }
120
+
121
+ // Handle array-contains specially
122
+ if (op === "array-contains") {
123
+ return { [field]: { $elemMatch: { $eq: value } } };
124
+ }
125
+ return { [field]: { [mongoOp]: value } };
126
+ }
127
+
128
+ /**
129
+ * Translate an `or(...)` / `and(...)` group, nesting included.
130
+ *
131
+ * Returns `undefined` for a group with nothing in it. `$or: []` is an error
132
+ * in Mongo and `$and: []` matches every document, so neither is a
133
+ * defensible reading of "no conditions".
134
+ */
135
+ static buildLogicalConditions(logical: LogicalCondition | undefined): Filter<Document> | undefined {
136
+ if (!logical || !Array.isArray(logical.conditions)) return undefined;
137
+
138
+ const parts: Filter<Document>[] = [];
139
+ for (const entry of logical.conditions) {
140
+ if (!entry) continue;
141
+ if ("type" in entry && "conditions" in entry) {
142
+ const nested = this.buildLogicalConditions(entry as LogicalCondition);
143
+ if (nested) parts.push(nested);
144
+ continue;
108
145
  }
146
+ const { column, operator, value } = entry as FilterCondition;
147
+ const condition = this.buildCondition(column, operator, value);
148
+ if (condition) parts.push(condition);
109
149
  }
110
150
 
111
- return conditions;
151
+ // Through the same combiners the rest of this class uses, so a
152
+ // one-condition group reads as the bare condition — identical to how
153
+ // `filter` would have expressed it — rather than as `{ $and: [x] }`.
154
+ return logical.type === "or"
155
+ ? this.combineConditionsWithOr(parts)
156
+ : this.combineConditionsWithAnd(parts);
112
157
  }
113
158
 
114
159
  /**
@@ -191,6 +236,13 @@ export class MongoConditionBuilder {
191
236
  */
192
237
  static buildQuery<M extends Record<string, any>>(options: {
193
238
  filter?: FilterValues<Extract<keyof M, string>>;
239
+ /**
240
+ * An `or(...)`/`and(...)` group, AND-ed with `filter` and
241
+ * `searchString` — the three are independent, as `FindParams`
242
+ * documents. Absent here until now, so a group that reached this
243
+ * driver was dropped and the read ran unfiltered.
244
+ */
245
+ logical?: LogicalCondition;
194
246
  searchString?: string;
195
247
  properties?: CollectionConfig["properties"];
196
248
  }): Filter<Document> {
@@ -202,6 +254,9 @@ export class MongoConditionBuilder {
202
254
  conditions.push(...filterConditions);
203
255
  }
204
256
 
257
+ const logicalCondition = this.buildLogicalConditions(options.logical);
258
+ if (logicalCondition) conditions.push(logicalCondition);
259
+
205
260
  // Add search conditions
206
261
  if (options.searchString && options.properties) {
207
262
  const searchConditions = this.buildSearchConditions(
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { Db, ObjectId, Collection, Document, FindOptions, Filter } from "mongodb";
9
- import { FilterValues, DataRepository, CollectionConfig, EntityReference } from "@rebasepro/types";
9
+ import { FilterValues, DataRepository, CollectionConfig, EntityReference, LogicalCondition } from "@rebasepro/types";
10
10
  import { MongoConditionBuilder } from "./MongoConditionBuilder";
11
11
  import { logger } from "@rebasepro/server";
12
12
 
@@ -193,9 +193,12 @@ export class MongoDataService implements DataRepository {
193
193
  collectionPath: string,
194
194
  options: {
195
195
  filter?: FilterValues<Extract<keyof M, string>>;
196
+ /** An `or(...)`/`and(...)` group, AND-ed with `filter`. */
197
+ logical?: LogicalCondition;
196
198
  orderBy?: string;
197
199
  order?: "desc" | "asc";
198
200
  limit?: number;
201
+ offset?: number;
199
202
  startAfter?: any;
200
203
  searchString?: string;
201
204
  databaseId?: string;
@@ -208,6 +211,7 @@ export class MongoDataService implements DataRepository {
208
211
  // Build query
209
212
  const query = options.rawQuery ?? MongoConditionBuilder.buildQuery<M>({
210
213
  filter: options.filter,
214
+ logical: options.logical,
211
215
  searchString: options.searchString,
212
216
  properties: options.collection?.properties ?? {}
213
217
  });
@@ -226,9 +230,14 @@ export class MongoDataService implements DataRepository {
226
230
  findOptions.limit = options.limit;
227
231
  }
228
232
 
229
- // Apply pagination (skip-based for now, cursor-based would be better)
233
+ // Apply pagination. `offset` is the parameter the REST layer and the
234
+ // SDK both speak; it was absent here, so every `?offset=` reaching this
235
+ // driver was discarded and page three served page one. `startAfter`
236
+ // stays as the older spelling and wins when both are given.
230
237
  if (options.startAfter !== undefined) {
231
238
  findOptions.skip = Number(options.startAfter);
239
+ } else if (options.offset) {
240
+ findOptions.skip = options.offset;
232
241
  }
233
242
 
234
243
  const docs = await collection.find(query, findOptions).toArray();
@@ -265,15 +274,25 @@ export class MongoDataService implements DataRepository {
265
274
  collectionPath: string,
266
275
  options: {
267
276
  filter?: FilterValues<Extract<keyof M, string>>;
277
+ /** An `or(...)`/`and(...)` group, AND-ed with `filter`. */
278
+ logical?: LogicalCondition;
279
+ searchString?: string;
280
+ collection?: CollectionConfig;
268
281
  databaseId?: string;
269
282
  rawQuery?: Filter<Document>;
270
283
  } = {}
271
284
  ): Promise<number> {
272
285
  const collection = this.getCollection(collectionPath);
273
286
 
274
- const query = options.rawQuery ?? (options.filter
275
- ? MongoConditionBuilder.buildQuery<M>({ filter: options.filter })
276
- : {});
287
+ // Every narrowing the listing applies has to apply here too, or the
288
+ // count describes a different query than the one it is reported
289
+ // against. `logical` and `searchString` were both missing.
290
+ const query = options.rawQuery ?? MongoConditionBuilder.buildQuery<M>({
291
+ filter: options.filter,
292
+ logical: options.logical,
293
+ searchString: options.searchString,
294
+ properties: options.collection?.properties ?? {}
295
+ });
277
296
 
278
297
  return collection.countDocuments(query);
279
298
  }
@@ -109,23 +109,17 @@ propertyCallbacks: undefined };
109
109
  /**
110
110
  * Fetch a collection of rows
111
111
  */
112
- async fetchCollection<M extends Record<string, any>>({
113
- path,
114
- collection,
115
- filter,
116
- limit,
117
- startAfter,
118
- orderBy,
119
- searchString,
120
- order
121
- }: FetchCollectionProps<M>): Promise<Record<string, unknown>[]> {
112
+ async fetchCollection<M extends Record<string, any>>(
113
+ props: FetchCollectionProps<M>
114
+ ): Promise<Record<string, unknown>[]> {
115
+ // Forwarded whole rather than re-listed. The hand-written list here
116
+ // named eight of the eleven fields `FetchCollectionProps` declares, so
117
+ // `logical` and `offset` were accepted by every type-checked boundary
118
+ // above and then dropped — an `or(...)` query ran unfiltered and
119
+ // `?offset=` served page one.
120
+ const { path, collection, ...query } = props;
122
121
  const rows = await this.dataService.fetchCollection<M>(path, {
123
- filter,
124
- limit,
125
- startAfter,
126
- orderBy,
127
- order,
128
- searchString,
122
+ ...query,
129
123
  collection: collection as CollectionConfig
130
124
  });
131
125
 
@@ -656,9 +650,18 @@ propertyCallbacks: undefined };
656
650
  async count<M extends Record<string, any>>({
657
651
  path,
658
652
  collection,
659
- filter
653
+ filter,
654
+ logical,
655
+ searchString
660
656
  }: FetchCollectionProps<M>): Promise<number> {
661
- return this.dataService.count<M>(path, { filter });
657
+ // The same narrowing the listing gets, or the total describes a
658
+ // different query than the rows it is reported beside.
659
+ return this.dataService.count<M>(path, {
660
+ filter,
661
+ logical,
662
+ searchString,
663
+ collection: collection as CollectionConfig
664
+ });
662
665
  }
663
666
 
664
667
  /**