@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.
- package/dist/db/MongoConditionBuilder.d.ts +25 -1
- package/dist/db/MongoConditionBuilder.d.ts.map +1 -1
- package/dist/db/MongoDataService.d.ts +8 -1
- package/dist/db/MongoDataService.d.ts.map +1 -1
- package/dist/index.es.js +78 -33
- package/dist/index.es.js.map +1 -1
- package/dist/services/MongoDriver.d.ts +2 -2
- package/dist/services/MongoDriver.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/db/MongoConditionBuilder.ts +93 -38
- package/src/db/MongoDataService.ts +24 -5
- package/src/services/MongoDriver.ts +21 -18
|
@@ -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>>(
|
|
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":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EACH,UAAU,EACV,WAAW,EAEX,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,SAAS,EAET,2BAA2B,EAC3B,IAAI,EACJ,YAAY,EAEZ,aAAa,EAEhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAQ5D;;;;;GAKG;AACH,qBAAa,WAAY,YAAW,UAAU;IAYtC,OAAO,CAAC,EAAE;aAGM,QAAQ,CAAC,EAAE,2BAA2B;IAd1D,GAAG,SAAa;IAChB,WAAW,UAAQ;IAEnB,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,eAAe,CAAuB;IACvC,cAAc,EAAE,mBAAmB,CAAC;IACpC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;gBAGjB,EAAE,EAAE,EAAE,EACd,eAAe,CAAC,EAAE,oBAAoB,EACtC,cAAc,CAAC,EAAE,mBAAmB,EACpB,QAAQ,CAAC,EAAE,2BAA2B,YAAA,EACtD,IAAI,CAAC,EAAE,IAAI;IAUf;;OAEG;IACH,WAAW,IAAI,IAAI;IAInB;;;OAGG;IACH,0BAA0B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,EAC3C,IAAI,EAAE,MAAM;;;;;;;;;;;IA2BhB;;OAEG;IACG,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,
|
|
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.
|
|
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/
|
|
44
|
-
"@rebasepro/
|
|
45
|
-
"@rebasepro/
|
|
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.
|
|
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 === "%")
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
logger.warn(`Unsupported filter operator: ${op}`);
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
114
|
+
const mongoOp = REBASE_TO_MONGO_OP[op];
|
|
98
115
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
/**
|