@davidtkramer/convex-relations 0.2.0 → 0.4.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 +33 -34
- package/dist/index.d.ts +16 -16
- package/dist/index.js +50 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,8 +126,6 @@ That works, but you are responsible for:
|
|
|
126
126
|
- [Error Semantics](#error-semantics)
|
|
127
127
|
- [Performance Characteristics](#performance-characteristics)
|
|
128
128
|
- [Comparison to `convex-helpers/server/relationships`](#comparison-to-convex-helpersserverrelationships)
|
|
129
|
-
- [Type Notes](#type-notes)
|
|
130
|
-
- [License](#license)
|
|
131
129
|
|
|
132
130
|
## Installation
|
|
133
131
|
|
|
@@ -157,12 +155,13 @@ wrappers. A minimal setup looks like this:
|
|
|
157
155
|
import { customCtx, customQuery } from "convex-helpers/server/customFunctions";
|
|
158
156
|
import { query as baseQuery } from "./_generated/server";
|
|
159
157
|
import type { DataModel } from "./_generated/dataModel";
|
|
158
|
+
import schema from "../schema";
|
|
160
159
|
import { createQueryFacade } from "@davidtkramer/convex-relations";
|
|
161
160
|
|
|
162
161
|
export const query = customQuery(
|
|
163
162
|
baseQuery,
|
|
164
163
|
customCtx((ctx: { db: any }) => ({
|
|
165
|
-
q: createQueryFacade<DataModel>(ctx.db),
|
|
164
|
+
q: createQueryFacade<DataModel>(ctx.db, schema),
|
|
166
165
|
})),
|
|
167
166
|
);
|
|
168
167
|
```
|
|
@@ -223,18 +222,26 @@ Single-field indexes accept a scalar. Compound indexes accept positional
|
|
|
223
222
|
arguments in index order. Zero-argument calls give you the indexed range so you
|
|
224
223
|
can filter, sort, paginate, or take a subset.
|
|
225
224
|
|
|
225
|
+
Because shorthand index methods like `.bySlug("...")` and
|
|
226
|
+
`.byPostIdAndStatus(...)` need the real indexed field names at runtime,
|
|
227
|
+
`createQueryFacade(...)` must be constructed with your Convex schema.
|
|
228
|
+
|
|
226
229
|
### `with(...)` builds nested result shapes
|
|
227
230
|
|
|
228
231
|
`with(...)` lets you attach additional fields to every document in a query. The
|
|
229
|
-
callback receives the current document
|
|
230
|
-
other query nodes or
|
|
232
|
+
callback receives the current document plus a small helper context, and returns
|
|
233
|
+
an object whose values can be plain sync values, other query nodes, or deferred
|
|
234
|
+
work via `defer(...)`.
|
|
231
235
|
|
|
232
236
|
```ts
|
|
233
237
|
const post = await ctx.q.posts
|
|
234
238
|
.bySlug("hello-world")
|
|
235
|
-
.with((post) => ({
|
|
239
|
+
.with((post, { defer }) => ({
|
|
236
240
|
author: ctx.q.authors.find(post.authorId),
|
|
237
241
|
comments: ctx.q.comments.byPostId(post._id).take(10),
|
|
242
|
+
readingTimeMinutes: defer(() =>
|
|
243
|
+
Math.ceil(post.body.split(/\s+/).length / 200),
|
|
244
|
+
),
|
|
238
245
|
}))
|
|
239
246
|
.unique();
|
|
240
247
|
```
|
|
@@ -259,26 +266,30 @@ of the result tree parallelizes across its sibling fields.
|
|
|
259
266
|
|
|
260
267
|
## API
|
|
261
268
|
|
|
262
|
-
### `createQueryFacade<DataModel>(db)`
|
|
269
|
+
### `createQueryFacade<DataModel>(db, schema)`
|
|
263
270
|
|
|
264
|
-
Creates a typed facade over your Convex `db`.
|
|
271
|
+
Creates a typed facade over your Convex `db`. Pass the runtime schema from
|
|
272
|
+
`defineSchema(...)` so indexed shorthand methods can map scalar and tuple
|
|
273
|
+
arguments onto the correct Convex index fields.
|
|
265
274
|
|
|
266
275
|
```ts
|
|
267
276
|
import { createQueryFacade } from "@davidtkramer/convex-relations";
|
|
268
277
|
import type { DataModel } from "./_generated/dataModel";
|
|
278
|
+
import schema from "../schema";
|
|
269
279
|
|
|
270
|
-
const q = createQueryFacade<DataModel>(ctx.db);
|
|
280
|
+
const q = createQueryFacade<DataModel>(ctx.db, schema);
|
|
271
281
|
```
|
|
272
282
|
|
|
273
|
-
### `
|
|
283
|
+
### `with(..., { defer })`
|
|
274
284
|
|
|
275
|
-
|
|
285
|
+
The `with(...)` callback context includes `defer`, which wraps arbitrary async
|
|
286
|
+
or sync work into the same lazy result tree as your relation queries.
|
|
276
287
|
|
|
277
288
|
```ts
|
|
278
289
|
const post = await q.posts
|
|
279
290
|
.bySlug("hello-world")
|
|
280
|
-
.with((post) => ({
|
|
281
|
-
readingTimeMinutes:
|
|
291
|
+
.with((post, { defer }) => ({
|
|
292
|
+
readingTimeMinutes: defer(() =>
|
|
282
293
|
Math.ceil(post.body.split(/\s+/).length / 200),
|
|
283
294
|
),
|
|
284
295
|
}))
|
|
@@ -364,10 +375,10 @@ Batch lookups skip missing rows.
|
|
|
364
375
|
```ts
|
|
365
376
|
const post = await q.posts
|
|
366
377
|
.bySlug("hello-world")
|
|
367
|
-
.with((post) => ({
|
|
378
|
+
.with((post, { defer }) => ({
|
|
368
379
|
author: q.authors.find(post.authorId),
|
|
369
380
|
comments: q.comments.byPostId(post._id).order("desc").take(10),
|
|
370
|
-
commentCount:
|
|
381
|
+
commentCount: defer(async () => {
|
|
371
382
|
const comments = await q.comments.byPostId(post._id).many();
|
|
372
383
|
return comments.length;
|
|
373
384
|
}),
|
|
@@ -407,12 +418,12 @@ const categories = await q.categories
|
|
|
407
418
|
This is essentially syntactic sugar for "run the source query, extract ids from
|
|
408
419
|
that field, then load the target rows for you."
|
|
409
420
|
|
|
410
|
-
You can also attach the source row
|
|
421
|
+
You can also attach the source row through the normal `with(...)` callback:
|
|
411
422
|
|
|
412
423
|
```ts
|
|
413
424
|
const categories = await q.categories
|
|
414
425
|
.through(q.postCategories.byPostId(postId).order("desc"), "categoryId")
|
|
415
|
-
.
|
|
426
|
+
.with((category, { source }) => ({ link: source }))
|
|
416
427
|
.many();
|
|
417
428
|
|
|
418
429
|
categories[0]?.link.postId;
|
|
@@ -427,7 +438,7 @@ timestamps.
|
|
|
427
438
|
```ts
|
|
428
439
|
const author = await q.authors
|
|
429
440
|
.through(q.posts.bySlug("hello-world"), "authorId")
|
|
430
|
-
.
|
|
441
|
+
.with((author, { source }) => ({ post: source }))
|
|
431
442
|
.unique();
|
|
432
443
|
|
|
433
444
|
author.post.slug;
|
|
@@ -445,12 +456,12 @@ const tags = await q.tags
|
|
|
445
456
|
.take(10),
|
|
446
457
|
"tagId",
|
|
447
458
|
)
|
|
448
|
-
.
|
|
459
|
+
.with((tag, { source }) => ({ link: source }))
|
|
449
460
|
.many();
|
|
450
461
|
```
|
|
451
462
|
|
|
452
463
|
After `through(...)`, you can keep shaping the target result with `with(...)`
|
|
453
|
-
and
|
|
464
|
+
and then choose a terminal like `many()` or `first()`.
|
|
454
465
|
|
|
455
466
|
## Terminals
|
|
456
467
|
|
|
@@ -517,10 +528,10 @@ const page = await q.posts.byAuthorId(authorId).paginate({
|
|
|
517
528
|
Within a single `with(...)` stage, every field in the returned object runs in parallel.
|
|
518
529
|
|
|
519
530
|
```ts
|
|
520
|
-
const post = await q.posts.find(postId).with((post) => ({
|
|
531
|
+
const post = await q.posts.find(postId).with((post, { defer }) => ({
|
|
521
532
|
author: q.authors.find(post.authorId),
|
|
522
533
|
comments: q.comments.byPostId(post._id).take(10),
|
|
523
|
-
categoryCount:
|
|
534
|
+
categoryCount: defer(async () => {
|
|
524
535
|
const categories = await q.categories
|
|
525
536
|
.through(q.postCategories.byPostId(post._id), "categoryId")
|
|
526
537
|
.many();
|
|
@@ -586,15 +597,3 @@ const categories = await getManyVia(
|
|
|
586
597
|
```
|
|
587
598
|
|
|
588
599
|
but also composes naturally with `with(...)`, `take(...)`, `firstOrNull()`, and typed nested traversal.
|
|
589
|
-
|
|
590
|
-
## Type Notes
|
|
591
|
-
|
|
592
|
-
- The facade is generic over your generated `DataModel`
|
|
593
|
-
- Table names, `_id` types, index names, and compound index prefixes are inferred
|
|
594
|
-
- Invalid table names and invalid index names are rejected at compile time
|
|
595
|
-
- Scalar shorthand is only allowed for single-field indexes
|
|
596
|
-
- Compound indexes use leading positional arguments
|
|
597
|
-
|
|
598
|
-
## License
|
|
599
|
-
|
|
600
|
-
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -43,16 +43,20 @@ type QueryPlanHandle<DataModel extends GenericDataModel, Table extends AppTable<
|
|
|
43
43
|
readonly _plan: QueryPlan;
|
|
44
44
|
readonly _table: Table;
|
|
45
45
|
};
|
|
46
|
-
type WithSpec = Record<string,
|
|
47
|
-
type
|
|
48
|
-
type
|
|
46
|
+
type WithSpec = Record<string, unknown>;
|
|
47
|
+
type DeferredBuilder = <Output>(load: () => Promise<Output> | Output) => QueryNode<Output>;
|
|
48
|
+
type BaseWithContext = {
|
|
49
|
+
defer: DeferredBuilder;
|
|
50
|
+
};
|
|
51
|
+
type WithContext<SourceItem = never> = BaseWithContext & ([SourceItem] extends [never] ? {} : {
|
|
52
|
+
source: SourceItem;
|
|
53
|
+
});
|
|
54
|
+
type WithBuilder<ParentItem, Context = BaseWithContext, Spec extends WithSpec | undefined = WithSpec | undefined> = (parent: ParentItem, context: Context) => Spec;
|
|
55
|
+
type AnyWithBuilder<ParentItem, Context = BaseWithContext> = WithBuilder<ParentItem, Context, WithSpec | undefined>;
|
|
49
56
|
type BuiltWithSpec<Builder> = Builder extends (...args: any[]) => infer Spec ? Spec : never;
|
|
50
57
|
type ExpandWith<ParentItem, Builder> = Simplify<ParentItem & (BuiltWithSpec<Builder> extends Record<string, unknown> ? {
|
|
51
|
-
[K in keyof BuiltWithSpec<Builder>]: BuiltWithSpec<Builder>[K] extends QueryNode<infer Output> ? Output :
|
|
58
|
+
[K in keyof BuiltWithSpec<Builder>]: BuiltWithSpec<Builder>[K] extends QueryNode<infer Output> ? Output : BuiltWithSpec<Builder>[K];
|
|
52
59
|
} : {})>;
|
|
53
|
-
type AttachSource<ParentItem, SourceItem, SourceKey extends string> = Simplify<ParentItem & {
|
|
54
|
-
[K in SourceKey]: SourceItem;
|
|
55
|
-
}>;
|
|
56
60
|
type PaginationOptions = {
|
|
57
61
|
numItems: number;
|
|
58
62
|
cursor: string | null;
|
|
@@ -89,12 +93,10 @@ type ThroughSourceField<DataModel extends GenericDataModel, TargetTable extends
|
|
|
89
93
|
[Field in Extract<keyof SourceItem, string>]: IdTargetTable<DataModel, SourceItem[Field]> extends TargetTable ? Field : never;
|
|
90
94
|
}[Extract<keyof SourceItem, string>];
|
|
91
95
|
type ManyThroughQueryBuilder<Item, SourceItem = unknown> = QueryNode<Item[]> & ThroughNodeHandle<SourceItem, 'many'> & {
|
|
92
|
-
with<Builder extends AnyWithBuilder<Item
|
|
93
|
-
withSource<const SourceKey extends string>(key: SourceKey): ManyThroughQueryBuilder<AttachSource<Item, SourceItem, SourceKey>, SourceItem>;
|
|
96
|
+
with<Builder extends AnyWithBuilder<Item, WithContext<SourceItem>>>(withBuilder: Builder): ManyThroughQueryBuilder<ExpandWith<Item, Builder>, SourceItem>;
|
|
94
97
|
};
|
|
95
98
|
type SingleThroughQueryBuilder<Item, SourceItem, Nullable extends boolean> = QueryNode<Nullable extends true ? Item | null : Item> & ThroughNodeHandle<SourceItem, Nullable extends true ? 'nullableSingle' : 'single'> & {
|
|
96
|
-
with<Builder extends AnyWithBuilder<Item
|
|
97
|
-
withSource<const SourceKey extends string>(key: SourceKey): SingleThroughQueryBuilder<AttachSource<Item, SourceItem, SourceKey>, SourceItem, Nullable>;
|
|
99
|
+
with<Builder extends AnyWithBuilder<Item, WithContext<SourceItem>>>(withBuilder: Builder): SingleThroughQueryBuilder<ExpandWith<Item, Builder>, SourceItem, Nullable>;
|
|
98
100
|
};
|
|
99
101
|
type TableQueryFacade<DataModel extends GenericDataModel, Table extends AppTable<DataModel>, Item = AppDoc<DataModel, Table>> = {
|
|
100
102
|
with<Builder extends AnyWithBuilder<Item>>(withBuilder: Builder): TableQueryFacade<DataModel, Table, ExpandWith<Item, Builder>>;
|
|
@@ -125,8 +127,7 @@ type TableBatchQueryFacade<DataModel extends GenericDataModel, Table extends App
|
|
|
125
127
|
many(): BatchQueryBuilder<Item>;
|
|
126
128
|
};
|
|
127
129
|
type ThroughQueryFacade<DataModel extends GenericDataModel, TargetTable extends AppTable<DataModel>, SourceItem, Item = AppDoc<DataModel, TargetTable>> = {
|
|
128
|
-
with<Builder extends AnyWithBuilder<Item
|
|
129
|
-
withSource<const SourceKey extends string>(key: SourceKey): ThroughQueryFacade<DataModel, TargetTable, SourceItem, AttachSource<Item, SourceItem, SourceKey>>;
|
|
130
|
+
with<Builder extends AnyWithBuilder<Item, WithContext<SourceItem>>>(withBuilder: Builder): ThroughQueryFacade<DataModel, TargetTable, SourceItem, ExpandWith<Item, Builder>>;
|
|
130
131
|
unique(): UniqueQueryBuilder<Item>;
|
|
131
132
|
uniqueOrNull(): UniqueOrNullQueryBuilder<Item>;
|
|
132
133
|
first(): FirstQueryBuilder<Item>;
|
|
@@ -173,9 +174,8 @@ type QuerySourcePlan = {
|
|
|
173
174
|
type QueryPlan = {
|
|
174
175
|
source: QuerySourcePlan;
|
|
175
176
|
modifiers: QueryModifier[];
|
|
176
|
-
expanders: AnyWithBuilder<any>[];
|
|
177
|
+
expanders: AnyWithBuilder<any, any>[];
|
|
177
178
|
};
|
|
178
179
|
declare function createQueryFacade<DataModel extends GenericDataModel>(db: GenericDatabaseReader<DataModel>): QueryFacade<DataModel>;
|
|
179
|
-
declare function compute<Output = unknown>(load: () => Promise<Output> | Output): QueryNode<Output>;
|
|
180
180
|
|
|
181
|
-
export { type AppDoc, type AppTable, type QueryFacade, type RootIndexValueArg, type StrictRootIndexValueArg, type UserIndex,
|
|
181
|
+
export { type AppDoc, type AppTable, type QueryFacade, type RootIndexValueArg, type StrictRootIndexValueArg, type UserIndex, createQueryFacade };
|
package/dist/index.js
CHANGED
|
@@ -8,31 +8,46 @@ function createQueryNode(executeRoot) {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
function createDeferredNode(load) {
|
|
12
|
+
return createQueryNode(async () => await load());
|
|
13
|
+
}
|
|
14
|
+
function isQueryNode(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && "_executeRoot" in value && typeof value._executeRoot === "function";
|
|
16
|
+
}
|
|
17
|
+
async function expandDoc(parent, withBuilder, context) {
|
|
18
|
+
const spec = withBuilder(parent, context) ?? {};
|
|
13
19
|
const entries = await Promise.all(
|
|
14
|
-
Object.entries(spec).map(
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
Object.entries(spec).map(async ([key, value]) => [
|
|
21
|
+
key,
|
|
22
|
+
isQueryNode(value) ? await value._executeRoot() : value
|
|
23
|
+
])
|
|
17
24
|
);
|
|
18
25
|
return {
|
|
19
26
|
...parent,
|
|
20
27
|
...Object.fromEntries(entries)
|
|
21
28
|
};
|
|
22
29
|
}
|
|
23
|
-
async function applyExpanders(item, expanders) {
|
|
30
|
+
async function applyExpanders(item, expanders, context) {
|
|
24
31
|
let current = item;
|
|
25
32
|
for (const expander of expanders) {
|
|
26
|
-
current = await expandDoc(current, expander);
|
|
33
|
+
current = await expandDoc(current, expander, context);
|
|
27
34
|
}
|
|
28
35
|
return current;
|
|
29
36
|
}
|
|
30
|
-
async function applyExpandersToMany(items, expanders) {
|
|
31
|
-
return await Promise.all(
|
|
37
|
+
async function applyExpandersToMany(items, expanders, getContext) {
|
|
38
|
+
return await Promise.all(
|
|
39
|
+
items.map((item, index) => applyExpanders(item, expanders, getContext(item, index)))
|
|
40
|
+
);
|
|
32
41
|
}
|
|
33
42
|
function buildQuery(makeQuery, modifiers) {
|
|
34
43
|
return modifiers.reduce((query, modifier) => modifier(query), makeQuery());
|
|
35
44
|
}
|
|
45
|
+
function createWithContext(source) {
|
|
46
|
+
return {
|
|
47
|
+
defer: createDeferredNode,
|
|
48
|
+
...source === void 0 ? {} : { source }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
36
51
|
function createPlan(source) {
|
|
37
52
|
return {
|
|
38
53
|
source,
|
|
@@ -209,14 +224,18 @@ function createPlanRuntime(db, plan) {
|
|
|
209
224
|
async function decorateItem(plan, rawItem, item) {
|
|
210
225
|
let output = item;
|
|
211
226
|
if (plan.expanders.length > 0) {
|
|
212
|
-
output = await applyExpanders(output, plan.expanders);
|
|
227
|
+
output = await applyExpanders(output, plan.expanders, createWithContext());
|
|
213
228
|
}
|
|
214
229
|
return output;
|
|
215
230
|
}
|
|
216
231
|
async function decorateItems(plan, rawItems, items) {
|
|
217
232
|
let output = items;
|
|
218
233
|
if (plan.expanders.length > 0) {
|
|
219
|
-
output = await applyExpandersToMany(
|
|
234
|
+
output = await applyExpandersToMany(
|
|
235
|
+
output,
|
|
236
|
+
plan.expanders,
|
|
237
|
+
() => createWithContext()
|
|
238
|
+
);
|
|
220
239
|
}
|
|
221
240
|
return output;
|
|
222
241
|
}
|
|
@@ -310,22 +329,25 @@ function createManyQueryBuilder(executeRoot) {
|
|
|
310
329
|
_throughSourceKind: "many"
|
|
311
330
|
};
|
|
312
331
|
}
|
|
313
|
-
async function decorateThroughItem(expanders,
|
|
332
|
+
async function decorateThroughItem(expanders, pair) {
|
|
314
333
|
let output = pair.target;
|
|
315
|
-
if (sourceKey) {
|
|
316
|
-
output = { ...output, [sourceKey]: pair.source };
|
|
317
|
-
}
|
|
318
334
|
if (expanders.length > 0) {
|
|
319
|
-
output = await applyExpanders(
|
|
335
|
+
output = await applyExpanders(
|
|
336
|
+
output,
|
|
337
|
+
expanders,
|
|
338
|
+
createWithContext(pair.source)
|
|
339
|
+
);
|
|
320
340
|
}
|
|
321
341
|
return output;
|
|
322
342
|
}
|
|
323
|
-
async function decorateThroughItems(expanders,
|
|
324
|
-
let output = pairs.map(
|
|
325
|
-
({ source, target }) => sourceKey ? { ...target, [sourceKey]: source } : target
|
|
326
|
-
);
|
|
343
|
+
async function decorateThroughItems(expanders, pairs) {
|
|
344
|
+
let output = pairs.map(({ target }) => target);
|
|
327
345
|
if (expanders.length > 0) {
|
|
328
|
-
output = await applyExpandersToMany(
|
|
346
|
+
output = await applyExpandersToMany(
|
|
347
|
+
output,
|
|
348
|
+
expanders,
|
|
349
|
+
(_item, index) => createWithContext(pairs[index].source)
|
|
350
|
+
);
|
|
329
351
|
}
|
|
330
352
|
return output;
|
|
331
353
|
}
|
|
@@ -336,7 +358,7 @@ async function executeThroughCollectionMany(db, plan) {
|
|
|
336
358
|
plan.targetField,
|
|
337
359
|
sourceItems
|
|
338
360
|
);
|
|
339
|
-
return await decorateThroughItems(plan.expanders,
|
|
361
|
+
return await decorateThroughItems(plan.expanders, pairs);
|
|
340
362
|
}
|
|
341
363
|
async function executeThroughCollectionFirst(db, plan) {
|
|
342
364
|
const pair = (await collectThroughPairsUntil(
|
|
@@ -348,7 +370,7 @@ async function executeThroughCollectionFirst(db, plan) {
|
|
|
348
370
|
if (!pair) {
|
|
349
371
|
throw new Error(`Could not find first ${plan.targetTable} through ${sourceDescription(plan.sourcePlan)}`);
|
|
350
372
|
}
|
|
351
|
-
return await decorateThroughItem(plan.expanders,
|
|
373
|
+
return await decorateThroughItem(plan.expanders, pair);
|
|
352
374
|
}
|
|
353
375
|
async function executeThroughCollectionFirstOrNull(db, plan) {
|
|
354
376
|
const pair = (await collectThroughPairsUntil(
|
|
@@ -357,7 +379,7 @@ async function executeThroughCollectionFirstOrNull(db, plan) {
|
|
|
357
379
|
iterateDecoratedSourceItems(db, plan.sourcePlan),
|
|
358
380
|
1
|
|
359
381
|
))[0];
|
|
360
|
-
return pair ? await decorateThroughItem(plan.expanders,
|
|
382
|
+
return pair ? await decorateThroughItem(plan.expanders, pair) : null;
|
|
361
383
|
}
|
|
362
384
|
async function executeThroughCollectionUnique(db, plan) {
|
|
363
385
|
const pairs = await collectThroughPairsUntil(
|
|
@@ -373,7 +395,7 @@ async function executeThroughCollectionUnique(db, plan) {
|
|
|
373
395
|
if (!pair) {
|
|
374
396
|
throw new Error(`Could not find ${plan.targetTable} through ${sourceDescription(plan.sourcePlan)}`);
|
|
375
397
|
}
|
|
376
|
-
return await decorateThroughItem(plan.expanders,
|
|
398
|
+
return await decorateThroughItem(plan.expanders, pair);
|
|
377
399
|
}
|
|
378
400
|
async function executeThroughCollectionUniqueOrNull(db, plan) {
|
|
379
401
|
const pairs = await collectThroughPairsUntil(
|
|
@@ -385,7 +407,7 @@ async function executeThroughCollectionUniqueOrNull(db, plan) {
|
|
|
385
407
|
if (pairs.length > 1) {
|
|
386
408
|
throw new Error("unique() returned more than one result");
|
|
387
409
|
}
|
|
388
|
-
return pairs[0] ? await decorateThroughItem(plan.expanders,
|
|
410
|
+
return pairs[0] ? await decorateThroughItem(plan.expanders, pairs[0]) : null;
|
|
389
411
|
}
|
|
390
412
|
async function executeThroughManyNode(db, plan) {
|
|
391
413
|
const sourceItems = await plan.sourceNode._executeRoot();
|
|
@@ -394,7 +416,7 @@ async function executeThroughManyNode(db, plan) {
|
|
|
394
416
|
plan.targetField,
|
|
395
417
|
sourceItems
|
|
396
418
|
);
|
|
397
|
-
return await decorateThroughItems(plan.expanders,
|
|
419
|
+
return await decorateThroughItems(plan.expanders, pairs);
|
|
398
420
|
}
|
|
399
421
|
async function executeThroughSingleNode(db, plan, nullable) {
|
|
400
422
|
const sourceItem = await plan.sourceNode._executeRoot();
|
|
@@ -415,7 +437,7 @@ async function executeThroughSingleNode(db, plan, nullable) {
|
|
|
415
437
|
}
|
|
416
438
|
throw new Error(`Could not find ${plan.targetTable} through source query`);
|
|
417
439
|
}
|
|
418
|
-
return await decorateThroughItem(plan.expanders,
|
|
440
|
+
return await decorateThroughItem(plan.expanders, pair);
|
|
419
441
|
}
|
|
420
442
|
function withThroughCollectionExpander(plan, expander) {
|
|
421
443
|
return {
|
|
@@ -423,32 +445,17 @@ function withThroughCollectionExpander(plan, expander) {
|
|
|
423
445
|
expanders: [...plan.expanders, expander]
|
|
424
446
|
};
|
|
425
447
|
}
|
|
426
|
-
function withThroughCollectionSourceKey(plan, sourceKey) {
|
|
427
|
-
return {
|
|
428
|
-
...plan,
|
|
429
|
-
sourceKey
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
448
|
function withThroughNodeExpander(plan, expander) {
|
|
433
449
|
return {
|
|
434
450
|
...plan,
|
|
435
451
|
expanders: [...plan.expanders, expander]
|
|
436
452
|
};
|
|
437
453
|
}
|
|
438
|
-
function withThroughNodeSourceKey(plan, sourceKey) {
|
|
439
|
-
return {
|
|
440
|
-
...plan,
|
|
441
|
-
sourceKey
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
454
|
function createThroughCollectionFacade(db, plan) {
|
|
445
455
|
return {
|
|
446
456
|
with(withBuilder) {
|
|
447
457
|
return createThroughCollectionFacade(db, withThroughCollectionExpander(plan, withBuilder));
|
|
448
458
|
},
|
|
449
|
-
withSource(key) {
|
|
450
|
-
return createThroughCollectionFacade(db, withThroughCollectionSourceKey(plan, key));
|
|
451
|
-
},
|
|
452
459
|
unique() {
|
|
453
460
|
return createSingleQueryBuilder(
|
|
454
461
|
async () => await executeThroughCollectionUnique(db, plan),
|
|
@@ -489,9 +496,6 @@ function createThroughManyQueryBuilder(db, plan) {
|
|
|
489
496
|
db,
|
|
490
497
|
withThroughNodeExpander(plan, withBuilder)
|
|
491
498
|
);
|
|
492
|
-
},
|
|
493
|
-
withSource(key) {
|
|
494
|
-
return createThroughManyQueryBuilder(db, withThroughNodeSourceKey(plan, key));
|
|
495
499
|
}
|
|
496
500
|
};
|
|
497
501
|
}
|
|
@@ -501,9 +505,6 @@ function createThroughSingleQueryBuilder(db, plan, nullable) {
|
|
|
501
505
|
_throughSourceKind: nullable ? "nullableSingle" : "single",
|
|
502
506
|
with(withBuilder) {
|
|
503
507
|
return createThroughSingleQueryBuilder(db, withThroughNodeExpander(plan, withBuilder), nullable);
|
|
504
|
-
},
|
|
505
|
-
withSource(key) {
|
|
506
|
-
return createThroughSingleQueryBuilder(db, withThroughNodeSourceKey(plan, key), nullable);
|
|
507
508
|
}
|
|
508
509
|
};
|
|
509
510
|
}
|
|
@@ -708,9 +709,6 @@ function createQueryFacade(db) {
|
|
|
708
709
|
}
|
|
709
710
|
);
|
|
710
711
|
}
|
|
711
|
-
function compute(load) {
|
|
712
|
-
return createQueryNode(async () => await load());
|
|
713
|
-
}
|
|
714
712
|
async function queryUniqueByIndex(db, table, index, value) {
|
|
715
713
|
if (index === "by_id") {
|
|
716
714
|
return await db.query(table).withIndex("by_id", (q) => q.eq("_id", value)).unique();
|
|
@@ -721,6 +719,5 @@ async function queryUniqueByIndex(db, table, index, value) {
|
|
|
721
719
|
).unique();
|
|
722
720
|
}
|
|
723
721
|
export {
|
|
724
|
-
compute,
|
|
725
722
|
createQueryFacade
|
|
726
723
|
};
|