flongo 2.1.0 → 2.2.0
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/README.md +46 -0
- package/dist/flongoCollection.d.ts +26 -0
- package/dist/flongoCollection.js +43 -0
- package/dist/flongoQuery.js +15 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -168,6 +168,32 @@ const topRated = await products.aggregate([
|
|
|
168
168
|
]);
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
+
#### Id projection & semi-joins (`getIds`)
|
|
172
|
+
|
|
173
|
+
When you only need to know **which** documents match — not their contents —
|
|
174
|
+
`getIds(query, pagination?)` returns matching `_id`s as strings. It uses a
|
|
175
|
+
server-side projection, so full documents never leave the server. This is the
|
|
176
|
+
natural fit for a semi-join: "of these N candidate ids, which pass this filter?"
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Of these candidate stayIds, which are publicly visible?
|
|
180
|
+
const visibleIds = await stays.getIds(
|
|
181
|
+
new FlongoQuery()
|
|
182
|
+
.where('_id').in(candidateStayIds)
|
|
183
|
+
.and('status').eq('Accepted')
|
|
184
|
+
.and('enable').eq(true)
|
|
185
|
+
);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`getIds` honors the same query and pagination semantics as `getAll`, including
|
|
189
|
+
sort clauses — ordered ids are useful for id-first pagination.
|
|
190
|
+
|
|
191
|
+
> **Note (2.2.0):** inline `_id` clauses now compose with other field clauses,
|
|
192
|
+
> as the example above relies on. Previously an `_id` clause caused every other
|
|
193
|
+
> clause in the same clause list to be silently dropped, and filters had to be
|
|
194
|
+
> attached via `.andQuery(new FlongoQuery().where('_id').in(ids))` — that
|
|
195
|
+
> pattern still works unchanged.
|
|
196
|
+
|
|
171
197
|
### Collection Operations
|
|
172
198
|
|
|
173
199
|
```typescript
|
|
@@ -184,6 +210,7 @@ const user = await collection.create({
|
|
|
184
210
|
// Read
|
|
185
211
|
const user = await collection.get('user123');
|
|
186
212
|
const users = await collection.getAll(query);
|
|
213
|
+
const ids = await collection.getIds(query); // matching _ids only (server-side projection)
|
|
187
214
|
const first = await collection.getFirst(query);
|
|
188
215
|
const count = await collection.count(query);
|
|
189
216
|
const exists = await collection.exists(query);
|
|
@@ -502,6 +529,25 @@ const users = await collection.getAll(
|
|
|
502
529
|
);
|
|
503
530
|
```
|
|
504
531
|
|
|
532
|
+
## Changelog
|
|
533
|
+
|
|
534
|
+
### 2.2.0
|
|
535
|
+
|
|
536
|
+
- **`FlongoCollection.getIds(query?, pagination?)`** — returns matching `_id`s
|
|
537
|
+
as strings via a server-side projection (`{_id: 1}` on `find`, `$project` on
|
|
538
|
+
the aggregation path), so full documents never cross the wire. Honors the
|
|
539
|
+
same query, sort, and pagination semantics as `getAll`.
|
|
540
|
+
- **Inline `_id` clauses now compose in `build()`.** Previously an `_id`
|
|
541
|
+
expression in the main clause list short-circuited the build and silently
|
|
542
|
+
discarded every other clause; combining `_id` with other filters required the
|
|
543
|
+
`.andQuery(new FlongoQuery().where('_id').in(ids))` workaround. Now `_id`
|
|
544
|
+
composes like any other field (string → ObjectId conversion is preserved for
|
|
545
|
+
both single values and `$in` arrays). Queries with **only** an `_id` clause
|
|
546
|
+
build identical output to before, and the `andQuery` pattern still works.
|
|
547
|
+
*Behavior note:* if a query combined `_id` with other clauses **and** relied
|
|
548
|
+
on those other clauses being ignored, it now returns only documents matching
|
|
549
|
+
all clauses — i.e., what the query literally says.
|
|
550
|
+
|
|
505
551
|
## License
|
|
506
552
|
|
|
507
553
|
MIT
|
|
@@ -98,6 +98,32 @@ export declare class FlongoCollection<T> {
|
|
|
98
98
|
* @private
|
|
99
99
|
*/
|
|
100
100
|
private getAllRandom;
|
|
101
|
+
/**
|
|
102
|
+
* Retrieves only the `_id`s of matching documents, as strings (the same
|
|
103
|
+
* normalization `toEntity` applies). Uses a server-side projection so full
|
|
104
|
+
* documents never leave the server — ideal for semi-joins ("which of these N
|
|
105
|
+
* ids match this filter?") and other membership checks where hydrating whole
|
|
106
|
+
* documents is wasted work.
|
|
107
|
+
*
|
|
108
|
+
* Honors the same query and pagination semantics as `getAll`, including sort
|
|
109
|
+
* clauses (ordered ids are useful for id-first pagination).
|
|
110
|
+
*
|
|
111
|
+
* Example usage:
|
|
112
|
+
* ```typescript
|
|
113
|
+
* // Semi-join: of these candidate stayIds, which are publicly visible?
|
|
114
|
+
* const visibleIds = await stays.getIds(
|
|
115
|
+
* new FlongoQuery()
|
|
116
|
+
* .where('_id').in(candidateStayIds)
|
|
117
|
+
* .and('status').eq('Accepted')
|
|
118
|
+
* .and('enable').eq(true)
|
|
119
|
+
* );
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @param query - Optional FlongoQuery for filtering
|
|
123
|
+
* @param pagination - Optional pagination settings
|
|
124
|
+
* @returns Promise resolving to the matching document ids as strings
|
|
125
|
+
*/
|
|
126
|
+
getIds(query?: FlongoQuery, pagination?: Pagination): Promise<string[]>;
|
|
101
127
|
/**
|
|
102
128
|
* Runs a raw aggregation pipeline against this collection and returns the
|
|
103
129
|
* documents with their `_id` normalized to a string (Entity form). A low-level
|
package/dist/flongoCollection.js
CHANGED
|
@@ -155,6 +155,49 @@ class FlongoCollection {
|
|
|
155
155
|
const res = await this.collection.aggregate(pipeline).toArray();
|
|
156
156
|
return res.map((d) => this.toEntity(d));
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Retrieves only the `_id`s of matching documents, as strings (the same
|
|
160
|
+
* normalization `toEntity` applies). Uses a server-side projection so full
|
|
161
|
+
* documents never leave the server — ideal for semi-joins ("which of these N
|
|
162
|
+
* ids match this filter?") and other membership checks where hydrating whole
|
|
163
|
+
* documents is wasted work.
|
|
164
|
+
*
|
|
165
|
+
* Honors the same query and pagination semantics as `getAll`, including sort
|
|
166
|
+
* clauses (ordered ids are useful for id-first pagination).
|
|
167
|
+
*
|
|
168
|
+
* Example usage:
|
|
169
|
+
* ```typescript
|
|
170
|
+
* // Semi-join: of these candidate stayIds, which are publicly visible?
|
|
171
|
+
* const visibleIds = await stays.getIds(
|
|
172
|
+
* new FlongoQuery()
|
|
173
|
+
* .where('_id').in(candidateStayIds)
|
|
174
|
+
* .and('status').eq('Accepted')
|
|
175
|
+
* .and('enable').eq(true)
|
|
176
|
+
* );
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @param query - Optional FlongoQuery for filtering
|
|
180
|
+
* @param pagination - Optional pagination settings
|
|
181
|
+
* @returns Promise resolving to the matching document ids as strings
|
|
182
|
+
*/
|
|
183
|
+
async getIds(query, pagination) {
|
|
184
|
+
// Mirror getAll: a seeded random sort must execute via the aggregation
|
|
185
|
+
// pipeline. Only the id column is materialized on either path.
|
|
186
|
+
if (query?.hasRandomSort()) {
|
|
187
|
+
await this.assertRandomSortSupported();
|
|
188
|
+
const pipeline = query.buildPipeline(pagination);
|
|
189
|
+
pipeline.push({ $project: { _id: 1 } });
|
|
190
|
+
const res = await this.collection.aggregate(pipeline).toArray();
|
|
191
|
+
return res.map((d) => String(d._id));
|
|
192
|
+
}
|
|
193
|
+
const mongodbQuery = query?.build() ?? {};
|
|
194
|
+
const mongodbOptions = {
|
|
195
|
+
...(query?.buildOptions(pagination) ?? new flongoQuery_1.FlongoQuery().buildOptions(pagination)),
|
|
196
|
+
projection: { _id: 1 }
|
|
197
|
+
};
|
|
198
|
+
const res = await this.collection.find(mongodbQuery, mongodbOptions).toArray();
|
|
199
|
+
return res.map((d) => String(d._id));
|
|
200
|
+
}
|
|
158
201
|
/**
|
|
159
202
|
* Runs a raw aggregation pipeline against this collection and returns the
|
|
160
203
|
* documents with their `_id` normalized to a string (Entity form). A low-level
|
package/dist/flongoQuery.js
CHANGED
|
@@ -492,25 +492,24 @@ class FlongoQuery {
|
|
|
492
492
|
// Process main expressions
|
|
493
493
|
if (this.expressions) {
|
|
494
494
|
for (const expression of this.expressions) {
|
|
495
|
-
//
|
|
495
|
+
// Build query object for this clause
|
|
496
|
+
let fieldValue;
|
|
496
497
|
if (expression.key === "_id") {
|
|
498
|
+
// _id keeps its string -> ObjectId conversion but composes with the
|
|
499
|
+
// other clauses like any field (it used to short-circuit the build,
|
|
500
|
+
// silently discarding every co-existing clause).
|
|
497
501
|
if (Array.isArray(expression.val)) {
|
|
498
502
|
// Multiple IDs: {_id: {$in: [ObjectId(...), ObjectId(...)]}}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
[expression.op ?? "$in"]: expression.val.map((element) => new mongodb_1.ObjectId(element))
|
|
502
|
-
}
|
|
503
|
+
fieldValue = {
|
|
504
|
+
[expression.op ?? "$in"]: expression.val.map((element) => new mongodb_1.ObjectId(element))
|
|
503
505
|
};
|
|
504
506
|
}
|
|
505
507
|
else {
|
|
506
508
|
// Single ID: {_id: ObjectId(...)}
|
|
507
|
-
|
|
509
|
+
fieldValue = new mongodb_1.ObjectId(expression.val);
|
|
508
510
|
}
|
|
509
|
-
break; // _id queries are typically exclusive
|
|
510
511
|
}
|
|
511
|
-
|
|
512
|
-
let fieldValue;
|
|
513
|
-
if (!expression.op) {
|
|
512
|
+
else if (!expression.op) {
|
|
514
513
|
// Direct value assignment for simple equality (arrContains)
|
|
515
514
|
fieldValue = expression.val;
|
|
516
515
|
}
|
|
@@ -528,10 +527,14 @@ class FlongoQuery {
|
|
|
528
527
|
// Standard operator
|
|
529
528
|
fieldValue = { [expression.op]: expression.val };
|
|
530
529
|
}
|
|
531
|
-
// Merge operators for the same field (e.g., range queries)
|
|
530
|
+
// Merge operators for the same field (e.g., range queries).
|
|
531
|
+
// ObjectId instances are direct values, not operator maps — spreading
|
|
532
|
+
// one would destroy it, so they always assign.
|
|
532
533
|
if (mongodbQuery[expression.key] &&
|
|
533
534
|
typeof mongodbQuery[expression.key] === "object" &&
|
|
534
|
-
|
|
535
|
+
!(mongodbQuery[expression.key] instanceof mongodb_1.ObjectId) &&
|
|
536
|
+
typeof fieldValue === "object" &&
|
|
537
|
+
!(fieldValue instanceof mongodb_1.ObjectId)) {
|
|
535
538
|
mongodbQuery[expression.key] = { ...mongodbQuery[expression.key], ...fieldValue };
|
|
536
539
|
}
|
|
537
540
|
else {
|