conradkoh
conradkoh6mo ago

convex-test fails for nested optional properties used in index

Hi, I am reporting a bug that happens in convex-test, when you use an optional property in an index. In this example, you see that add_info is an optional property (by way of being excluded in one of the types within a discriminated union type). 1. There is no type error 2. The test fails in convex-test 3. The test passes against the live convex server I've also create a full reproduction with different scenarios (refer to commit history) here. - https://github.com/conradkoh/convex-test-index-bug-repo/commits/master/ Thanks in advance. Schema
export default defineSchema({
user: defineTable(
v.union(
v.object({
timestamp: v.number(),
type: v.literal('without_age'),
name: v.string(),
}),
v.object({
timestamp: v.number(),
type: v.literal('with_age'),
name: v.string(),
add_info: v.object({
age: v.number(),
}),
})
)
).index('by_age_timestamp', ['add_info.age', 'timestamp']),
});
export default defineSchema({
user: defineTable(
v.union(
v.object({
timestamp: v.number(),
type: v.literal('without_age'),
name: v.string(),
}),
v.object({
timestamp: v.number(),
type: v.literal('with_age'),
name: v.string(),
add_info: v.object({
age: v.number(),
}),
})
)
).index('by_age_timestamp', ['add_info.age', 'timestamp']),
});
Query Code
const users = await ctx.db
.query('user')
.withIndex('by_age_timestamp', (f) =>
f.eq('add_info.age', age).gt('timestamp', 0)
)
.collect();
const users = await ctx.db
.query('user')
.withIndex('by_age_timestamp', (f) =>
f.eq('add_info.age', age).gt('timestamp', 0)
)
.collect();
Test
it('local: should not return users that have no age', async () => {
const t = convexTest(schema);
await t.mutation(api.user.create, createUserParams);
const users = await t.query(api.user.list, queryParams);
expect(users).toEqual([]);
});
it('local: should not return users that have no age', async () => {
const t = convexTest(schema);
await t.mutation(api.user.create, createUserParams);
const users = await t.query(api.user.list, queryParams);
expect(users).toEqual([]);
});
Error
TypeError: Cannot convert undefined or null to object
❯ isSimpleObject node_modules/convex-test/dist/index.js:415:30
413| const isSimple = prototype === null ||
414| prototype === Object.prototype ||
415| // Objects generated from other contexts (e.…
| ^
TypeError: Cannot convert undefined or null to object
❯ isSimpleObject node_modules/convex-test/dist/index.js:415:30
413| const isSimple = prototype === null ||
414| prototype === Object.prototype ||
415| // Objects generated from other contexts (e.…
| ^
12 Replies
conradkoh
conradkohOP6mo ago
also import to note in the repro that generally, there are issues detecting the generated folder when using pnpm, but in yarn it works fine.
conradkoh
conradkohOP6mo ago
@Michal Srb I'm not sure if this is the correct fix, but I created a PR to the main repo here: https://github.com/get-convex/convex-test/pull/10
GitHub
ensure that null values are not simple objects by conradkoh · Pull ...
Fix crashes in test when using nested optional properties in an index There is no type error The test fails in convex-test The test passes against the live convex server Schema export default def...
conradkoh
conradkohOP6mo ago
the issue I think is that typeof null is "object", and this was a case not considered in the implementation of the function. also, supplementing the other lines of the stack trace for info
FAIL convex/transaction.test.ts > creates transactions
TypeError: Cannot convert undefined or null to object
❯ isSimpleObject node_modules/convex-test/dist/index.js:414:30
412| const isSimple = prototype === null ||
413| prototype === Object.prototype ||
414| // Objects generated from other contexts (e.…
| ^
415| // conditions but are still simple objects.
416| prototype?.constructor?.name === "Object";
❯ evaluateFieldPath node_modules/convex-test/dist/index.js:426:18
❯ evaluateRangeFilter node_modules/convex-test/dist/index.js:493:20
❯ node_modules/convex-test/dist/index.js:265:56
❯ node_modules/convex-test/dist/index.js:265:38
❯ DatabaseFake._iterateDocs node_modules/convex-test/dist/index.js:232:21
❯ DatabaseFake._evaluateQuery node_modules/convex-test/dist/index.js:264:22
❯ DatabaseFake.startQuery node_modules/convex-test/dist/index.js:181:30
FAIL convex/transaction.test.ts > creates transactions
TypeError: Cannot convert undefined or null to object
❯ isSimpleObject node_modules/convex-test/dist/index.js:414:30
412| const isSimple = prototype === null ||
413| prototype === Object.prototype ||
414| // Objects generated from other contexts (e.…
| ^
415| // conditions but are still simple objects.
416| prototype?.constructor?.name === "Object";
❯ evaluateFieldPath node_modules/convex-test/dist/index.js:426:18
❯ evaluateRangeFilter node_modules/convex-test/dist/index.js:493:20
❯ node_modules/convex-test/dist/index.js:265:56
❯ node_modules/convex-test/dist/index.js:265:38
❯ DatabaseFake._iterateDocs node_modules/convex-test/dist/index.js:232:21
❯ DatabaseFake._evaluateQuery node_modules/convex-test/dist/index.js:264:22
❯ DatabaseFake.startQuery node_modules/convex-test/dist/index.js:181:30
lee
lee6mo ago
thanks for the bug report! I don't see any nulls in your example; if a field is missing it's considered undefined for the purpose of index equality but you're probably right about the prototype thing. I don't know off the top of my head how prototype works for null and undefined 🙂
conradkoh
conradkohOP6mo ago
@lee from the little I know about what I can see, I believe there is a false assumption somewhere that an the value of the index (split by .) will always result in a value that can be evaluated by Object.getPrototypeOf. unfortunately, when you call Object.getPrototypeOf(null) or Object.getPrototypeOf(undefined), it will throw an exception rather than return null. this results in the error TypeError: Cannot convert undefined or null to object
lee
lee6mo ago
yep makes sense. Good find
ampp
ampp6mo ago
I notice that using array[0] without the proper check throws errors in tests but not bundled code sent to convex.
const result = await ctx.table("table").take(1);
const newId = await insertMutation(ctx, {
row: result[0]._id,
const result = await ctx.table("table").take(1);
const newId = await insertMutation(ctx, {
row: result[0]._id,
obviously you should just do .first() or do result && await. but to make both happy a null check for like result[0] works. That's a poor example but i know its also not respecting working code in my event system, i have actions deep not getting triggered, and going through a new debug process for just tests is not fun. SO its either a error from having too many nested functions or something like that above. also looks like my query works even if i use .api instead of .internal on the function calls
conradkoh
conradkohOP6mo ago
@lee don't meant to add any pressure - but is there a certain date that the team would target to do a release of the fix? sorry, probably not great to be asking this over the weekend!
Michal Srb
Michal Srb6mo ago
I'll fix this on Monday @conradkoh @ampp that doesn't sounds like the same issue as in this thread? Can you open a new thread with the reproduction?
conradkoh
conradkohOP6mo ago
thanks Michal!
Michal Srb
Michal Srb6mo ago
@conradkoh Fixed, please upgrade.
conradkoh
conradkohOP6mo ago
thanks @Michal Srb, this fixes it.

Did you find this page helpful?