TripleSpeeder
TripleSpeeder
CCConvex Community
Created by TripleSpeeder on 12/14/2024 in #support-community
After moving from Clerk to Convex-Auth my convex tests fail
I'm pretty sure that everything in convex folder is fine, convex dev does not report any problems. But all my tests are now failing with this import error:
Error: Cannot find module '/home/michael/WebstormProjects/bikeproto/node_modules/@convex-dev/auth/dist/server/oauth/checks' imported from /home/michael/WebstormProjects/bikeproto/node_modules/@convex-dev/auth/dist/server/oauth/callback.js
Error: Cannot find module '/home/michael/WebstormProjects/bikeproto/node_modules/@convex-dev/auth/dist/server/oauth/checks' imported from /home/michael/WebstormProjects/bikeproto/node_modules/@convex-dev/auth/dist/server/oauth/callback.js
I'm probably missing something obvious? Already wiped my node_modules and reinstalled all dependencies, but no bueno.
37 replies
CCConvex Community
Created by TripleSpeeder on 6/7/2024 in #support-community
typed useQuery mock
I'm trying to make a quick mock for useQuery. It's basically working, but I would like to have the query typed. This is the current implementation:
export const useQuery = fn((query, ...args) => {
if (args[0] === "skip") {
return undefined;
}
const queryName = getFunctionName(query);
switch (queryName) {
case "manufacturer/queries:getManufacturerQuery":
return manufacturer;
default:
throw new Error(
`Unexpected query call ${queryName}! Please add a mock :)`,
);
...

export const useQuery = fn((query, ...args) => {
if (args[0] === "skip") {
return undefined;
}
const queryName = getFunctionName(query);
switch (queryName) {
case "manufacturer/queries:getManufacturerQuery":
return manufacturer;
default:
throw new Error(
`Unexpected query call ${queryName}! Please add a mock :)`,
);
...

What are the correct types for query and ...args? I would like the return of getFunctionName to be typed based on my schema/api. Is this possible?
4 replies
CCConvex Community
Created by TripleSpeeder on 6/3/2024 in #support-community
Error testing file upload
This is probably something stupid on my side... But maybe someone can help 🙂 I'm testing image upload with convex-test. For simplicity i just create a 100 Byte Blob like this:
const image = new Blob([new ArrayBuffer(100)]);
// Store the image in Convex
const storageId = await t.run(async (ctx) => {
return ctx.storage.store(image);
});
const image = new Blob([new ArrayBuffer(100)]);
// Store the image in Convex
const storageId = await t.run(async (ctx) => {
return ctx.storage.store(image);
});
However this throws the following error inside store function:
TypeError: blob.arrayBuffer is not a function
at blobSha (/home/michael/dev/prototest/node_modules/convex-test/dist/index.js:833:36)
TypeError: blob.arrayBuffer is not a function
at blobSha (/home/michael/dev/prototest/node_modules/convex-test/dist/index.js:833:36)
When I use the blob from a real image it works as expected:
const imageUrl = "https://fakeimg.pl/800x600/";
const response = await fetch(imageUrl);
const image = await response.blob();
// Store the image in Convex
const storageId = await t.run(async (ctx) => {
return ctx.storage.store(image);
});
const imageUrl = "https://fakeimg.pl/800x600/";
const response = await fetch(imageUrl);
const image = await response.blob();
// Store the image in Convex
const storageId = await t.run(async (ctx) => {
return ctx.storage.store(image);
});
Any idea what I'm doing wrong when creating the Blob data?
11 replies
CCConvex Community
Created by TripleSpeeder on 4/30/2024 in #support-community
index on string field - how to handle case (in)sensitivity?
I have a minor issue where I'm not sure whats the best approach. I have a simple "manufacturer" table with a name field. I frequently need to select entries based on name, so have an index on the name field. Enduser can create new entries, but the name should be unique. So when the user tries to create a new entry, the first thing I'm looking at is existing entries with that name:
// prevent creation of duplicate manufacturer
const existingManufacturer = await db
.query("manufacturer")
.withIndex("byName", (q) => q.eq("name", manufacturerData.name))
.unique();
if (existingManufacturer) {
// use existing entry...
} else {
// create new entry...
}
// prevent creation of duplicate manufacturer
const existingManufacturer = await db
.query("manufacturer")
.withIndex("byName", (q) => q.eq("name", manufacturerData.name))
.unique();
if (existingManufacturer) {
// use existing entry...
} else {
// create new entry...
}
Now I need to handle case mismatch. If I have an existing entry like Special Brand and the user tries to create a new one in lowercase special brand it should use the existing entry. Naive approach is to convert everything always to lowercase, so the database only contains lowercase names and any string provided by enduser gets converted to lowercase before processing. But since I'm dealing with brand names I need to preserve casing, as that may be a part of the brand identity. E.g. names like "bigBETty" should be stored exactly. My current idea to handle this is to store the name twice - One lowercase variant that is used for indexing and searching, and one "display" variant that is only used for display purpose and keeps the exact spelling/casing. Does this make sense? Or are there better approaches?
3 replies
CCConvex Community
Created by TripleSpeeder on 4/22/2024 in #support-community
no "and" filter in convexTest?
Using convexTest I'm getting
Error: not implemented: {"$and":[{"$eq":[{"$field":"manufacturerId"},{"$literal":"10001;manufacturer"}]},{"$eq":[{"$field":"model"},{"$literal":"Test Model"}]}]}
Error: not implemented: {"$and":[{"$eq":[{"$field":"manufacturerId"},{"$literal":"10001;manufacturer"}]},{"$eq":[{"$field":"model"},{"$literal":"Test Model"}]}]}
The query looks like this:
.query("bikeModel")
.filter((q) =>
q.and(
q.eq(q.field("manufacturerId"), manufacturerId),
q.eq(q.field("model"), bikeModelData.model),
),
)
.unique();
.query("bikeModel")
.filter((q) =>
q.and(
q.eq(q.field("manufacturerId"), manufacturerId),
q.eq(q.field("model"), bikeModelData.model),
),
)
.unique();
Looks like the and filter is not implemented, or am I missing something?
4 replies
CCConvex Community
Created by TripleSpeeder on 2/19/2024 in #support-community
How can i map/extend the results of a paginatedQuery?
I'm running a paginated query on a lookup table that returns objects with reference to the actual document. How can i do this? Below is the query i tried to use, but when calling loadMore i get this error:
InvalidCursor: Tried to run a query starting from a cursor, but it looks like this cursor is from a different query.
InvalidCursor: Tried to run a query starting from a cursor, but it looks like this cursor is from a different query.
Relevant part from the query:
// get user. If no userId is provided, use currently logged-in user
const user = feedUserId
? await db.get(feedUserId)
: await getCurrentUser(ctx);

const personalFeedItemsPage = await db
.query("personalFeed")
.withIndex("byUserId", (q) =>
q.eq("userId", user?._id || ("" as Id<"users">)),
)
.paginate(paginationOpts);

// map results from personalFeedItems to actual feedItems
const itemsPage = pruneNull(
await asyncMap(personalFeedItemsPage.page, (personalFeedItem) =>
db.get(personalFeedItem.feedItemId),
),
);

return {
...personalFeedItemsPage,
page: itemsPage,
};
// get user. If no userId is provided, use currently logged-in user
const user = feedUserId
? await db.get(feedUserId)
: await getCurrentUser(ctx);

const personalFeedItemsPage = await db
.query("personalFeed")
.withIndex("byUserId", (q) =>
q.eq("userId", user?._id || ("" as Id<"users">)),
)
.paginate(paginationOpts);

// map results from personalFeedItems to actual feedItems
const itemsPage = pruneNull(
await asyncMap(personalFeedItemsPage.page, (personalFeedItem) =>
db.get(personalFeedItem.feedItemId),
),
);

return {
...personalFeedItemsPage,
page: itemsPage,
};
I'm assuming the problem is that I'm returning a page of Doc<"feedItem"> instead of `Doc<"PersonalFeedItem">... What would be a better approach here?
4 replies
CCConvex Community
Created by TripleSpeeder on 1/5/2024 in #support-community
Caching behaviour of HttpClient
I'm seriously confused now, trying to understand how/why convex HttpClient is caching responses. I have this convex query:
export const getUserByUsername = query({
args: {
username: v.string(),
},
handler: async (ctx, { username }) => {
console.log(`getUserByUsername ${username}...`);
const result = await ctx.db
.query("users")
.withIndex("by_clerk_username", (q) =>
q.eq("clerkUser.username", username),
)
.unique();
console.log(
`getUserByUsername ${username} result description:`,
result?.description,
);
return result;
},
});
export const getUserByUsername = query({
args: {
username: v.string(),
},
handler: async (ctx, { username }) => {
console.log(`getUserByUsername ${username}...`);
const result = await ctx.db
.query("users")
.withIndex("by_clerk_username", (q) =>
q.eq("clerkUser.username", username),
)
.unique();
console.log(
`getUserByUsername ${username} result description:`,
result?.description,
);
return result;
},
});
The query is logging the description field of the username. Now I modify the description field by a mutation. But when rendering the profile page for that user ( using ConvexHTTPclient, not the useQuery hook), I still get the old value. Initially I thought that this is an issue with next.js caching, but after drilling through all layers I think the problem is on convex side? My next dev terminal logs the following console output:
[CONVEX Q(users:getUserByUsername)] [LOG] 'getUserByUsername mike...'
[CONVEX Q(users:getUserByUsername)] [LOG] 'getUserByUsername mike result description:' 'bbbbbbb'
[CONVEX Q(users:getUserByUsername)] [LOG] 'getUserByUsername mike...'
[CONVEX Q(users:getUserByUsername)] [LOG] 'getUserByUsername mike result description:' 'bbbbbbb'
But i have changed the description to 'XXXXXXX' before! When I run the same query through npx convex run i get the expected result 'XXXXXXX' My understanding is that by seeing the CONVEX Q... log in my terminal that indeed the convex DB is hit, so any internal caches of next.js are not involved anymore. But looking at the convex dashboard logs, there is no according log entry created, so where is the output in my terminal coming from? It seems for me that the convex-internal DB cache is not invalidated by my mutation that updates the users table. But why do i get the correct result through npx convex run then? Pls halp 🙂
11 replies
CCConvex Community
Created by TripleSpeeder on 1/2/2024 in #support-community
best practices/examples to use httpClient with unstable_cache
For some rather static parts of my app i want to use next.js server-side rendering, making use of the data cache to reduce the number of queries hitting convex layer. So I'm using the ConvexHttpClient and wrap it in unstable_cache from next/cache. A working example:
const getCachedBikeModel = unstable_cache(
async (bikeModelId: Id<"bikeModel">) => {
return client.query(api.bikeModel.getFullBikeModelQuery, {
bikeModelId,
});
},
["getFullBikeModel"],
{
revalidate: 60, // 1 minute
},
);
const getCachedBikeModel = unstable_cache(
async (bikeModelId: Id<"bikeModel">) => {
return client.query(api.bikeModel.getFullBikeModelQuery, {
bikeModelId,
});
},
["getFullBikeModel"],
{
revalidate: 60, // 1 minute
},
);
In the component i can then use it with const bikeModel = await getCachedBikeModel(bikeModelId) This works as expected, but it's a lot of boilerplate to manually create wrappers. Is there any example/guide how I can automatically generate these from the api?
19 replies
CCConvex Community
Created by TripleSpeeder on 1/1/2024 in #support-community
Whats the correct type for FileMetadata now?
Since getFileMetadata is deprecated, I'm changing my code to use db.system.get(<storageId>). As the docs point out, the return is not anymore of type "FileMetadata". But what is the correct type now?
3 replies
CCConvex Community
Created by TripleSpeeder on 12/15/2023 in #support-community
Impersonate user / create test content
I'm working on a site that has a lot of user-generated content. I'm wondering what strategy I can follow to generate test content? Using the import command looks difficult, as my schema has a lot of relations, so i would need to carefully craft the import data making sure to always reference the correct IDs. I would prefer to just call the regular mutations to create data, but most mutations are requiring a logged in user. Are there any best practices/suggestions how to approach this? Maybe there is some way to impersonate a user? So i would create a bunch of test users via import of json data, then call the regular mutations to create content, impersonating one of the previously created test users.
7 replies
CCConvex Community
Created by TripleSpeeder on 11/30/2023 in #support-community
Strange error when trying to use search
I'm getting a strange error as soon as I'm trying to search on a table with searchIndex. Browser console keeps printing this, repeating forever:
WebSocket closed with code 1011: InternalServerError
_app-62f3ee54ceeb705a.js:4 Attempting reconnect in 51.837452158347006ms
_app-62f3ee54ceeb705a.js:4 WebSocket reconnected
_app-62f3ee54ceeb705a.js:4 WebSocket closed with code 1011: InternalServerError
_app-62f3ee54ceeb705a.js:4 Attempting reconnect in 67.02649050867504ms
_app-62f3ee54ceeb705a.js:4 WebSocket reconnected
WebSocket closed with code 1011: InternalServerError
_app-62f3ee54ceeb705a.js:4 Attempting reconnect in 51.837452158347006ms
_app-62f3ee54ceeb705a.js:4 WebSocket reconnected
_app-62f3ee54ceeb705a.js:4 WebSocket closed with code 1011: InternalServerError
_app-62f3ee54ceeb705a.js:4 Attempting reconnect in 67.02649050867504ms
_app-62f3ee54ceeb705a.js:4 WebSocket reconnected
I'm accessing this function from the convex dashboard:
export const testsearch = query({
args: {
query: v.optional(v.string()),
},
handler: async (ctx, { query }) => {
if (query && query.length > 0) {
return ctx.db
.query("partModel")
.withSearchIndex("search_component", (q) =>
q.search("component", query),
)
.collect();
} else {
return ctx.db.query("partModel").collect();
}
},
});
export const testsearch = query({
args: {
query: v.optional(v.string()),
},
handler: async (ctx, { query }) => {
if (query && query.length > 0) {
return ctx.db
.query("partModel")
.withSearchIndex("search_component", (q) =>
q.search("component", query),
)
.collect();
} else {
return ctx.db.query("partModel").collect();
}
},
});
When not providing a query the result is as expected. When providing a query string, the UI keeps loading forever and console shows above errors.
7 replies
CCConvex Community
Created by TripleSpeeder on 11/28/2023 in #support-community
"We're unable to display your source code"
I keep getting this message when accessing my functions/queries/mutations in the dashboard. Source code is shown for 2 rather simple functions, so it seems to work sometimes... Do I need to take care of special formatting or similar in my convex files so they can be shown correctly?
6 replies
CCConvex Community
Created by TripleSpeeder on 10/20/2023 in #support-community
Is there a way to get the object type from a db.get result?
I want to create a generic Like table like this:
like: defineTable({
userId: v.id("users"),
targetId: v.union(
v.id("bike"),
v.id("part"),
v.id("event"),
);,
})
like: defineTable({
userId: v.id("users"),
targetId: v.union(
v.id("bike"),
v.id("part"),
v.id("event"),
);,
})
So i can use the same table for storing likes to bikes, parts or events. This works fine, but I'm struggling with one aspect: I would like to create a page where a users sees all objects he has liked. I can get the actual target object (bike, part or event) of a like with this query:
const like = await ctx.db.get(likeId);
return await ctx.db.get(like.targetId);
const like = await ctx.db.get(likeId);
return await ctx.db.get(like.targetId);
Problem is I don't know the actual type of the target object. And I need this information to display accordingly in the frontend. Is there some generic/built-in way to get the actual type of the get response?
15 replies
CCConvex Community
Created by TripleSpeeder on 10/4/2023 in #support-community
verify Table ID type in a form with zod
I want to submit an id of convex type Id<"part"> with a form. I'm doing form validation with zod and wondering what is the best way to do this. If i use z.string() for the part ID, typescript complains that string can not be assigned to Id<"part"> when calling the mutation function. This sounds reasonable, as the mutation indeed expects an argument of type Id<"part">. I get correct types and no typescript error when using z.custom<Id<"part">>() to validate the field. But I think I still need to add a validator function to this custom type, because zod says If you don't provide a validation function, Zod will allow any value. I can add a manual check to make sure the provided value is indeed a string, and maybe check length and whatnot. But I'm wondering if I'm missing something here... Is there a best practice for a scenario like this?
4 replies
CCConvex Community
Created by TripleSpeeder on 9/28/2023 in #support-community
Enforce unique?
My data model has a "slug" component so I have readable urls like /blog/my-new-entry/ instead of /blog/3fp6dtpeg3rxcxfbexp2cd3r9jp7kxg. Is there a way to enforce uniqueness of the slugfield on db level? I know i can use query(...).unique() but throwing here is a bit too late 🙂
8 replies