@davidtkramer/convex-relations 0.4.2 → 0.5.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 +38 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,7 @@ That works, but you are responsible for:
|
|
|
123
123
|
- [Relation Expansion with `with(...)`](#relation-expansion-with-with)
|
|
124
124
|
- [Reference Traversal with `through(...)`](#reference-traversal-with-through)
|
|
125
125
|
- [Terminals](#terminals)
|
|
126
|
+
- [In-Memory Shaping](#in-memory-shaping)
|
|
126
127
|
- [Error Semantics](#error-semantics)
|
|
127
128
|
- [Performance Characteristics](#performance-characteristics)
|
|
128
129
|
- [Comparison to `convex-helpers/server/relationships`](#comparison-to-convex-helpersserverrelationships)
|
|
@@ -513,6 +514,43 @@ const page = await q.posts.byAuthorId(authorId).paginate({
|
|
|
513
514
|
});
|
|
514
515
|
```
|
|
515
516
|
|
|
517
|
+
## In-Memory Shaping
|
|
518
|
+
|
|
519
|
+
`many()` and `take(count)` return lazy nodes. Chain `filter(...)` or `sort(...)`
|
|
520
|
+
on them to shape the loaded rows in memory before the node resolves. Both run
|
|
521
|
+
after `with(...)` expansions, so they can read expanded relations, which the
|
|
522
|
+
database-side `filter(...)` on a range query cannot.
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
const comments = await q.comments
|
|
526
|
+
.byPostId(postId)
|
|
527
|
+
.with((comment) => ({ author: q.authors.findOrNull(comment.authorId) }))
|
|
528
|
+
.many()
|
|
529
|
+
.filter((comment): comment is typeof comment & { author: Author } =>
|
|
530
|
+
comment.author !== null,
|
|
531
|
+
)
|
|
532
|
+
.sort((a, b) => b.author.reputation - a.author.reputation);
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
A type-predicate `filter(...)` narrows the result type. `sort(...)` requires a
|
|
536
|
+
comparator and never mutates the loaded array.
|
|
537
|
+
|
|
538
|
+
The shaped node is still a lazy node: it works as a `with(...)` value and as a
|
|
539
|
+
`through(...)` source.
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
const post = await q.posts.find(postId).with((post) => ({
|
|
543
|
+
approvedComments: q.comments
|
|
544
|
+
.byPostId(post._id)
|
|
545
|
+
.many()
|
|
546
|
+
.filter((comment) => comment.status === "approved"),
|
|
547
|
+
}));
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Prefer the database-side `filter(...)` and `order(...)` on the range query when
|
|
551
|
+
the condition only involves the row's own fields; in-memory shaping still loads
|
|
552
|
+
and expands every row before discarding it.
|
|
553
|
+
|
|
516
554
|
## Error Semantics
|
|
517
555
|
|
|
518
556
|
- `find(...)` throws if the document is missing
|
package/dist/index.d.ts
CHANGED
|
@@ -80,7 +80,11 @@ type FirstQueryBuilder<Item> = SingleQueryBuilder<Item, false>;
|
|
|
80
80
|
type FirstOrNullQueryBuilder<Item> = SingleQueryBuilder<Item, true>;
|
|
81
81
|
type FindQueryBuilder<Item> = ExpandableSingleQueryBuilder<Item, false>;
|
|
82
82
|
type FindOrNullQueryBuilder<Item> = ExpandableSingleQueryBuilder<Item, true>;
|
|
83
|
-
type ManyQueryBuilder<Item> = QueryNode<Item[]> & ThroughNodeHandle<Item, 'many'
|
|
83
|
+
type ManyQueryBuilder<Item> = QueryNode<Item[]> & ThroughNodeHandle<Item, 'many'> & {
|
|
84
|
+
filter<Narrowed extends Item>(predicate: (item: Item, index: number) => item is Narrowed): ManyQueryBuilder<Narrowed>;
|
|
85
|
+
filter(predicate: (item: Item, index: number) => unknown): ManyQueryBuilder<Item>;
|
|
86
|
+
sort(compare: (a: Item, b: Item) => number): ManyQueryBuilder<Item>;
|
|
87
|
+
};
|
|
84
88
|
type BatchQueryBuilder<Item> = ManyQueryBuilder<Item>;
|
|
85
89
|
type ThroughSourceNodeKind = 'many' | 'single' | 'nullableSingle';
|
|
86
90
|
type ThroughNodeHandle<SourceItem, Kind extends ThroughSourceNodeKind> = {
|
package/dist/index.js
CHANGED
|
@@ -69,7 +69,9 @@ function withExpander(plan, expander) {
|
|
|
69
69
|
}
|
|
70
70
|
function normalizeIndexValues(index, value, indexFields) {
|
|
71
71
|
if (Array.isArray(value)) {
|
|
72
|
-
return Object.fromEntries(
|
|
72
|
+
return Object.fromEntries(
|
|
73
|
+
indexFields.slice(0, value.length).map((field, index2) => [field, value[index2]])
|
|
74
|
+
);
|
|
73
75
|
}
|
|
74
76
|
if (isPlainObject(value)) {
|
|
75
77
|
return value;
|
|
@@ -339,7 +341,15 @@ function createSingleQueryBuilder(executeRoot, nullable) {
|
|
|
339
341
|
function createManyQueryBuilder(executeRoot) {
|
|
340
342
|
return {
|
|
341
343
|
...createQueryNode(executeRoot),
|
|
342
|
-
_throughSourceKind: "many"
|
|
344
|
+
_throughSourceKind: "many",
|
|
345
|
+
filter(predicate) {
|
|
346
|
+
return createManyQueryBuilder(
|
|
347
|
+
async () => (await executeRoot()).filter((item, index) => predicate(item, index))
|
|
348
|
+
);
|
|
349
|
+
},
|
|
350
|
+
sort(compare) {
|
|
351
|
+
return createManyQueryBuilder(async () => [...await executeRoot()].sort(compare));
|
|
352
|
+
}
|
|
343
353
|
};
|
|
344
354
|
}
|
|
345
355
|
async function decorateThroughItem(expanders, pair) {
|