convex-helpers
Version:
A collection of useful code to complement the official convex package.
180 lines (179 loc) • 8.1 kB
JavaScript
import { asyncMap, nullThrows } from "..";
/**
* getAll returns a list of Documents (or null) for the `Id`s passed in.
*
* Nulls are returned for documents not found.
* @param db A DatabaseReader, usually passed from a mutation or query ctx.
* @param ids An list (or other iterable) of Ids pointing to a table.
* @returns The Documents referenced by the Ids, in order. `null` if not found.
*/
export async function getAll(db, ids) {
return asyncMap(ids, db.get);
}
/**
* getAllOrThrow returns a list of Documents for the `Id`s passed in.
*
* It throws if any documents are not found (null).
* @param db A DatabaseReader, usually passed from a mutation or query ctx.
* @param ids An list (or other iterable) of Ids pointing to a table.
* @returns The Documents referenced by the Ids, in order. `null` if not found.
*/
export async function getAllOrThrow(db, ids) {
return await asyncMap(ids, async (id) => nullThrows(await db.get(id)));
}
function firstIndexField(index, field) {
if (field)
return field;
if (index.startsWith("by_"))
return index.slice(3);
return index;
}
/**
* Get a document matching the given value for a specified field.
*
* `null` if not found.
* Useful for fetching a document with a one-to-one relationship via backref.
* Requires the table to have an index on the field named the same as the field.
* e.g. `defineTable({ fieldA: v.string() }).index("fieldA", ["fieldA"])`
*
* Getting 'string' is not assignable to parameter of type 'never'?
* Make sure your index is named after your field.
*
* @param db DatabaseReader, passed in from the function ctx
* @param table The table to fetch the target document from.
* @param index The index on that table to look up the specified value by.
* @param value The value to look up the document by, often an ID.
* @param field The field on that table that should match the specified value.
* Optional if the index is named after the field.
* @returns The document matching the value, or null if none found.
*/
export async function getOneFrom(db, table, index, value, ...fieldArg) {
const field = firstIndexField(index, fieldArg[0]);
return db
.query(table)
.withIndex(index, (q) => q.eq(field, value))
.unique();
}
/**
* Get a document matching the given value for a specified field.
*
* Throws if not found.
* Useful for fetching a document with a one-to-one relationship via backref.
* Requires the table to have an index on the field named the same as the field.
* e.g. `defineTable({ fieldA: v.string() }).index("fieldA", ["fieldA"])`
*
* Getting 'string' is not assignable to parameter of type 'never'?
* Make sure your index is named after your field.
*
* @param db DatabaseReader, passed in from the function ctx
* @param table The table to fetch the target document from.
* @param index The index on that table to look up the specified value by.
* @param value The value to look up the document by, often an ID.
* @param field The field on that table that should match the specified value.
* Optional if the index is named after the field.
* @returns The document matching the value. Throws if not found.
*/
export async function getOneFromOrThrow(db, table, index, value, ...fieldArg) {
const field = firstIndexField(index, fieldArg[0]);
const ret = await db
.query(table)
.withIndex(index, (q) => q.eq(field, value))
.unique();
return nullThrows(ret, `Can't find a document in ${table} with field ${field} equal to ${value}`);
}
/**
* Get a list of documents matching the given value for a specified field.
*
* Useful for fetching many documents related to a given value via backrefs.
* Requires the table to have an index on the field named the same as the field.
* e.g. `defineTable({ fieldA: v.string() }).index("fieldA", ["fieldA"])`
*
* Getting 'string' is not assignable to parameter of type 'never'?
* Make sure your index is named after your field.
*
* @param db DatabaseReader, passed in from the function ctx
* @param table The table to fetch the target document from.
* @param index The index on that table to look up the specified value by.
* @param value The value to look up the document by, often an ID.
* @param field The field on that table that should match the specified value.
* Optional if the index is named after the field.
* @returns The documents matching the value, if any.
*/
export async function getManyFrom(db, table, index, value, ...fieldArg) {
const field = firstIndexField(index, fieldArg[0]);
return db
.query(table)
.withIndex(index, (q) => q.eq(field, value))
.collect();
}
// many-to-many via lookup table
/**
* Get related documents by using a join table.
*
* Any missing documents referenced by the join table will be null.
* It will find all join table entries matching a value, then look up all the
* documents pointed to by the join table entries. Useful for many-to-many
* relationships.
*
* Requires your join table to have an index on the fromField named the same as
* the fromField, and another field that is an Id type.
* e.g. `defineTable({ a: v.string(), b: v.id("users") }).index("a", ["a"])`
*
* Getting 'string' is not assignable to parameter of type 'never'?
* Make sure your index is named after your field.
*
* @param db DatabaseReader, passed in from the function ctx
* @param table The table to fetch the target document from.
* @param toField The ID field on the table pointing at target documents.
* @param index The index on the join table to look up the specified value by.
* @param value The value to look up the documents in join table by.
* @param field The field on the join table to match the specified value.
* Optional if the index is named after the field.
* @returns The documents targeted by matching documents in the table, if any.
*/
export async function getManyVia(db, table, toField, index, value, ...fieldArg) {
return await asyncMap(await getManyFrom(db, table, index, value, ...fieldArg), async (link) => {
const id = link[toField];
try {
return await db.get(id);
}
catch {
return await db.system.get(id);
}
});
}
/**
* Get related documents by using a join table.
*
* Throws an error if any documents referenced by the join table are missing.
* It will find all join table entries matching a value, then look up all the
* documents pointed to by the join table entries. Useful for many-to-many
* relationships.
*
* Requires your join table to have an index on the fromField named the same as
* the fromField, and another field that is an Id type.
* e.g. `defineTable({ a: v.string(), b: v.id("users") }).index("a", ["a"])`
*
* Getting 'string' is not assignable to parameter of type 'never'?
* Make sure your index is named after your field.
*
* @param db DatabaseReader, passed in from the function ctx
* @param table The table to fetch the target document from.
* @param toField The ID field on the table pointing at target documents.
* @param index The index on the join table to look up the specified value by.
* @param value The value to look up the documents in join table by.
* @param field The field on the join table to match the specified value.
* Optional if the index is named after the field.
* @returns The documents targeted by matching documents in the table, if any.
*/
export async function getManyViaOrThrow(db, table, toField, index, value, ...fieldArg) {
return await asyncMap(await getManyFrom(db, table, index, value, ...fieldArg), async (link) => {
const id = link[toField];
try {
return nullThrows(await db.get(id), `Can't find document ${id} referenced in ${table}'s field ${toField} for ${fieldArg[0] ?? index} equal to ${value}`);
}
catch {
return nullThrows(await db.system.get(id), `Can't find document ${id} referenced in ${table}'s field ${toField} for ${fieldArg[0] ?? index} equal to ${value}`);
}
});
}