@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 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 and returns an object whose values are
230
- other query nodes or `compute(...)` calls.
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
- ### `compute(load)`
283
+ ### `with(..., { defer })`
274
284
 
275
- Wraps arbitrary async or sync work so it can be used inside `with(...)`.
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: compute(() =>
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: compute(async () => {
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 with `withSource(...)`:
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
- .withSource("link")
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
- .withSource("post")
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
- .withSource("link")
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 `withSource(...)`, then choose a terminal like `many()` or `first()`.
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: compute(async () => {
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, QueryNode<any>>;
47
- type WithBuilder<ParentItem, Spec extends WithSpec | undefined = WithSpec | undefined> = (parent: ParentItem) => Spec;
48
- type AnyWithBuilder<ParentItem> = WithBuilder<ParentItem, WithSpec | undefined>;
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 : never;
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>>(withBuilder: Builder): ManyThroughQueryBuilder<ExpandWith<Item, Builder>, SourceItem>;
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>>(withBuilder: Builder): SingleThroughQueryBuilder<ExpandWith<Item, Builder>, SourceItem, Nullable>;
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>>(withBuilder: Builder): ThroughQueryFacade<DataModel, TargetTable, SourceItem, ExpandWith<Item, Builder>>;
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, compute, createQueryFacade };
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
- async function expandDoc(parent, withBuilder) {
12
- const spec = withBuilder(parent) ?? {};
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
- async ([key, query]) => [key, await query._executeRoot()]
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(items.map((item) => applyExpanders(item, expanders)));
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(output, plan.expanders);
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, sourceKey, pair) {
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(output, expanders);
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, sourceKey, pairs) {
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(output, expanders);
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, plan.sourceKey, pairs);
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, plan.sourceKey, pair);
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, plan.sourceKey, pair) : null;
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, plan.sourceKey, pair);
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, plan.sourceKey, pairs[0]) : null;
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, plan.sourceKey, pairs);
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, plan.sourceKey, pair);
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidtkramer/convex-relations",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Typed query facade helpers for Convex backends",
5
5
  "type": "module",
6
6
  "license": "MIT",