Riki
Riki
CCConvex Community
Created by Riki on 1/24/2025 in #support-community
React-Native / Jest / Convex-test setup issue
No description
8 replies
CCConvex Community
Created by Riki on 1/18/2025 in #support-community
Convex Auth with Expo and Native Apple and Google Sign In
Hello, I am currently following the documentation for using Convex Auth with Expo. However, the documentation provides an example to sign up with the in app web-browser when used with Apple or Google Sign In. Although I get why it's done that way, as Convex Auth is in an early stage and still in beta, but displaying a webview to sign in with Apple/Google in an app is not the perfect experience for users. I would like to use Convex Auth but with expo-apple-authentication and @react-native-google-signin/google-signin that allows to provide native experience sign-in. Question: How should I do to implement it? I guess something like I should code the isLoading, isAuthenticated, and fetchAccessToken within a useAuthXXX hook? But it seems refreshing the access token with Apple is not straight-forward, so not sure if I am not digging into a hole that is going to take me a large amount of time.
3 replies
CCConvex Community
Created by Riki on 12/15/2024 in #support-community
.paginate combined with .withIndex is causing an error
let say I have two tables in my scheme that represents a user that lives in a city and posts that are made in a city. All the code below could be better arranged, but this is for the simplicity of the example:
const userTable = defineTable({
name: v.string()
cityId: v.string()
})

const postTable = defineTable({
message: v.string()
cityId: v.string()
})
const userTable = defineTable({
name: v.string()
cityId: v.string()
})

const postTable = defineTable({
message: v.string()
cityId: v.string()
})
Let's say I want to display all the posts from the city of the user, so this is how I achieve it:
export const getPostForUser = query({
args: {
userId: v.id("user")
paginationOpts: paginationOptsValidator
},
handler: async (ctx, args) => {
const user = await ctx.db.get(args.userId)
if (user == null) { throw new ConvexError("Error") }
const storiesByUsers = await ctx.db
.query('post')
.withIndex('by_cityId', q => q.eq('cityId', user.cityId))
.paginate(args.paginationOpts);
}
})
export const getPostForUser = query({
args: {
userId: v.id("user")
paginationOpts: paginationOptsValidator
},
handler: async (ctx, args) => {
const user = await ctx.db.get(args.userId)
if (user == null) { throw new ConvexError("Error") }
const storiesByUsers = await ctx.db
.query('post')
.withIndex('by_cityId', q => q.eq('cityId', user.cityId))
.paginate(args.paginationOpts);
}
})
The problem is that when a user change his city by patching his data, the getPostForUser function returns an error: Uncaught Error: InvalidCursor: Tried to run a query starting from a cursor, but it looks like this cursor is from a different query. I think this happens because the args haven't changed but the result has, given it has been retriggered by the inner data change from the user table. A way to circumvent this would be to instead pass the city of the user as an argument of the function, so the query result changes when the args change. However, I would like to improve the ergonomics here: I feel that the code above looks good and it is unfortunate not being able to rely on a table for a paginated query. I don't think it makes sense this function returns an error conceptually.
30 replies
CCConvex Community
Created by Riki on 12/11/2024 in #support-community
[Paginated Query] How to get the first elements in the "middle"
Context: Let's say we build a chat in a mobile app: it consists of a screen that displays messages vertically. In order to achieve this, it makes sense to have a paginated query where messages are ordered by _creationTime . Let's say a user is off for one week, have hundreds of missing messages, and open the chat. A common feature is to display the last seen message and then the new ones, and to scroll down until the most recent one. Problem: How do we do to "start" the paginated query so that it returns items started from the LAST_MESSAGE_SEEN and can scroll up or down to load more? We could load of course all the messages until the LAST_MESSAGE_SEEN , and scroll to it, but that wouldn't scale much if hundred of messages were not read, and wouldn't give the chat a snappy feeling.
// Conceptually, the message list would look like this
[...OLD_SEEN_MESSAGES, LAST_MESSAGE_SEEN, ...NEW_UNREAD_MESSAGES]
// Conceptually, the message list would look like this
[...OLD_SEEN_MESSAGES, LAST_MESSAGE_SEEN, ...NEW_UNREAD_MESSAGES]
7 replies
CCConvex Community
Created by Riki on 9/11/2024 in #support-community
[React-Native] Custom Auth - JWT refresh issue
Hello, forwarding the comments on this thread to this new one. Problem: When the app is in background/inactive, the JWT can expire during this period. When the app is put in foreground, it seems that the convex queries are rerun before a new JWT is fetched. A symptom we see is that the ctx.auth.getUserIdentity() function returns null on Convex backend.
16 replies
CCConvex Community
Created by Riki on 8/30/2024 in #support-community
[Convex Dashboard] Editing nested field (with union?) is causing an error
Hello team, I think there is an issue with the last Convex update: When I want to edit an union with nested fields in the dashboard (v.union(v.object(), v.object()) it reports an error, even if I respect the schema (in particular, even if I don't do any modification). Example of validator causing an issue:
export const messagesValidator = v.object({
foo: v.string(), // works fine
nestedData: v.union( // Can not edit
v.object({
kind: v.literal('TEXT'),
text: v.string(),
}),
v.object({
kind: v.literal('IMAGE'),
uri: v.string(),
}),
),
export const messagesValidator = v.object({
foo: v.string(), // works fine
nestedData: v.union( // Can not edit
v.object({
kind: v.literal('TEXT'),
text: v.string(),
}),
v.object({
kind: v.literal('IMAGE'),
uri: v.string(),
}),
),
Find attached a demo video. Thanks in advance for your work, and on a side-note I am pretty excited to watch this Convex/TanStack video you published yesterday 😀
29 replies
CCConvex Community
Created by Riki on 8/1/2024 in #support-community
Errors Ergonomics
Hello, two questions regarding Errors, Auth and Pagination Context: I use firebase authentication that I inject within my convex queries with the auth hook. So, at the end of the day, I get server-side the auth id of my users thanks to ctx.auth.getUserIdentity(). I used the approach to throw a ConvexError on the server if some query requests are not authenticated according to this doc. Problem: 1) It seems like the queries are still running when the JWT expires. So the queries throw an error because getUserIdentity returns undefined. I am not very used to auth, but wouldn't it be better that the convex auth hook in the client fetches a new JWT when the JWT is expired and goes to loading mode meanwhile? 2) When I hit an auth error, although that's not optimal, I decided to return "empty" values. Ever undefined for normal queries or an empty pagination response. I did it like below for pagination. However that still makes the UI crashes and convex servers are returning the following when my JWT expires: Uncaught Error: InvalidCursor: Tried to run a query starting from a cursor, but it looks like this cursor is from a different query. at async handler (../convex/usersVisited.ts:107:17). Is there a convenient value that is exported from convex to model a generic empty pagination result?
let authId: string;
try {
authId = await ensureAuthenticated(ctx);
} catch (error) {
return await ctx.db
.query('usersVisited')
.withIndex('by_toUserId_count', q =>
q.eq('toUserId', authId).gte('createdAt', 0).lt('createdAt', 0),
)
.order('desc')
.paginate(args.paginationOpts);
}

const lowerBounds = ....
const upperBounds = ....
const visitors = await ctx.db
.query('usersVisited')
.withIndex('by_toUserId_count', q =>
q
.eq('toUserId', authId)
.gte('createdAt', lowerBound)
.lt('createdAt', upperBound),
)
.order('desc')
.paginate(args.paginationOpts);
let authId: string;
try {
authId = await ensureAuthenticated(ctx);
} catch (error) {
return await ctx.db
.query('usersVisited')
.withIndex('by_toUserId_count', q =>
q.eq('toUserId', authId).gte('createdAt', 0).lt('createdAt', 0),
)
.order('desc')
.paginate(args.paginationOpts);
}

const lowerBounds = ....
const upperBounds = ....
const visitors = await ctx.db
.query('usersVisited')
.withIndex('by_toUserId_count', q =>
q
.eq('toUserId', authId)
.gte('createdAt', lowerBound)
.lt('createdAt', upperBound),
)
.order('desc')
.paginate(args.paginationOpts);
Thanks in advance!
1 replies
CCConvex Community
Created by Riki on 7/15/2024 in #support-community
Pagination and Self-Reference
Let's say two users can be friends. To model this, I used the following approach:
schema.ts

const users = defineTable({ ... })

const friendsRelationships = defineTable({
user1Id: v.id("users"),
user2Id: v.id("users")
schema.ts

const users = defineTable({ ... })

const friendsRelationships = defineTable({
user1Id: v.id("users"),
user2Id: v.id("users")
Then user1Id and user2Id can be populated by some user Ids. However, what if I want to retrieve all the friends of a given user that are paginated. Given a user id can either be in user1Id or user2Id it looks like this is not possible with the current approach. As an alternative, we could duplicate all the relationships and swap user1Id and user2Id so that I can paginate by indexing user1Id or user2Id, but this doesn't look optimal to duplicate those in the same table.
6 replies
CCConvex Community
Created by Riki on 7/10/2024 in #support-community
Same query used multiple times
Context: The result of a query is used quite a lot of time within my React-Native app, in particular into mutliple screen and deep component tree leaves. This can happen quite a lot, as in React-Native with React-Navigation, compared to the Web, you can have a lot of screens already mounted within the navigation stack (infinite in theory). And those screens subscribes to the same convex query. Question: In that scenario, what is the good practice? Solution 1), juste write the convex query everywhere, but with the risk of multiplicating by quite much the websocket connections. Is this correct that everytime I write "useQuery" it opens a new connection, regardless if the same query and same args is called somewhere else? Solution 2) Introduce somehow a singleton with a state management solution. Call one time the convex query, hydrate the app global state, and components subscribe to this global state. The drawback is that it is a bit cumbersome to introduce this global state that somehow duplicate the convex one. Which solution should would you recommend to pick?
3 replies
CCConvex Community
Created by Riki on 7/2/2024 in #support-community
Args in Log Streams
Hello, I am currently using Axiom to monitor my convex backend as per showcased in the documentation. When checking and debugging the errors reported by convex to Axiom, I usually want to know what happened in my function: what args and userIdentity where passed to the failing function in order to understand and debug it. But I can not find them reported in Axiom. Would it be possible that convex send those args to the Log Stream pipeline automatically? Not sure what entry should be used, but in any cases, that would be very useful. Otherwise, I'll probably need to write a console.log every time I create a function or modify args which is kinda error-prone. I am kinda new to monitoring/observability, so this may be a dumb question, or there may be already a solution. Just asking in case of. Have a nice day and thanks for your work the convex team!
13 replies
CCConvex Community
Created by Riki on 6/11/2024 in #support-community
withSearchIndex with diacritical signs
Hello, Context: I am working on a feature to search users by name within my app. Problem: However I noticed that if I don't type diacritical signs that are present on some user names, they do not appear in my search. For instance, one of my user is called: Adélaïde . If I look for Adelaide, the withSearchIndex function won't return that user. Overall after some testing, I believe that diacritical signs are considered as a totally different letter with the current searchIndex. Request: I think letters without diacritical signs should be considered close to the original letter. Not sure if it's easily feasible on your side and if you are able to introduce a ponderation somehow (instead of a YES/NO). Thanks and have a nice day!
5 replies
CCConvex Community
Created by Riki on 5/30/2024 in #support-community
Convex-test and by_creation_time
Hello, when testing a function that uses the index "by_creation_time" , convex-test are failing and reporting an error. It looks like to me the code is valid, so I believe it's a convex-test issue: Example: Code
export const deleteNotifications = internalMutation({
handler: async ctx => {
const now = Date.now();
const staleTimestamp = now - 30 * 24 * 60 * 60 * 1000;
const notificationsToDelete = await ctx.db
.query('notifications')
.withIndex('by_creation_time', q => q.lt('_creationTime', staleTimestamp))
.take(500);

await asyncMap(notificationsToDelete, async notification => {
await ctx.db.delete(notification._id);
});
},
});
export const deleteNotifications = internalMutation({
handler: async ctx => {
const now = Date.now();
const staleTimestamp = now - 30 * 24 * 60 * 60 * 1000;
const notificationsToDelete = await ctx.db
.query('notifications')
.withIndex('by_creation_time', q => q.lt('_creationTime', staleTimestamp))
.take(500);

await asyncMap(notificationsToDelete, async notification => {
await ctx.db.delete(notification._id);
});
},
});
Test
import {convexTest} from 'convex-test';
import {expect, test, vi} from 'vitest';
import schema from './schema';
import {internal} from './_generated/api';

test('Delete old notifications', async () => {
// ...
const t = convexTest(schema);
await t.mutation(internal.notifications.deleteNotifications);
// ...
import {convexTest} from 'convex-test';
import {expect, test, vi} from 'vitest';
import schema from './schema';
import {internal} from './_generated/api';

test('Delete old notifications', async () => {
// ...
const t = convexTest(schema);
await t.mutation(internal.notifications.deleteNotifications);
// ...
Error while running tests
Cannot use index "by_creation_time" for table "notifications" because it is not declared in the schema.
Cannot use index "by_creation_time" for table "notifications" because it is not declared in the schema.
Just reporting the issue, thanks in advance if you fix it. Have a nice day Convex team 🙋‍♂️
6 replies
CCConvex Community
Created by Riki on 4/29/2024 in #support-community
Convex-test and React-Native
Given react-native compatibility with vitest is experimental and convex-test recommends to use vitest, has someone been able to use convex-test with React-Native? Eventually by using jest?
13 replies
CCConvex Community
Created by Riki on 3/22/2024 in #support-community
Secret files
Hello, I would like to send push notifications from some convex action. In order to achieve it, I need to install the firebase-admin SDK on my convex environment. I setup an action in a NodeJS environment, but according to the firebase doc (https://firebase.google.com/docs/admin/setup#initialize_the_sdk_in_non-google_environments) I need to store a GOOGLE_APPLICATION_CREDENTIALS file in my server. I saw in the Convex doc that we can set env variables, but I don't know if there is also a way to store secret files. Thanks in advance!
12 replies
CCConvex Community
Created by Riki on 3/12/2024 in #support-community
Optimizing query with joins and criteria on the last table
Hello, I wanted to know if there is a way/pattern to optimize the following query: Context: I have an entity users , events, and the many-to-many join table usersEvents. Goal: Given a user, I want to see its upcoming next 5 events (date sorted). What I have done so far:
export const getUserProfile = query({
args: {userId: v.id("users")},
handler: async (ctx, args) => {
const user = await ctx.db.get(args.userId)

const events = await asyncMap(
await getManyFrom(
ctx.db,
'usersEvents',
'userId',
args.userId,
),
link =>
ctx.db
.query('events')
.withIndex('by_id_date', q =>
q.eq('id', link.eventId).gt('date', Date.now()),
)
.unique(),
);
return {
user: user,
// I'll sort by date and take the 5 first items on client side
events: events,
};
},
});
export const getUserProfile = query({
args: {userId: v.id("users")},
handler: async (ctx, args) => {
const user = await ctx.db.get(args.userId)

const events = await asyncMap(
await getManyFrom(
ctx.db,
'usersEvents',
'userId',
args.userId,
),
link =>
ctx.db
.query('events')
.withIndex('by_id_date', q =>
q.eq('id', link.eventId).gt('date', Date.now()),
)
.unique(),
);
return {
user: user,
// I'll sort by date and take the 5 first items on client side
events: events,
};
},
});
Problem: This is not optimized given I retrieve/scan all the future events of my user => Bandwidth consumption increase. I just would like to take the 5 next ones happening in the future. If someone could help me/give me some light to optimize this query. Thanks in advance 🙏
10 replies