Son
Son7mo ago

How to use useMutation with convex-ents. API object TYPE error

in my frontend i'm calling this api const storeUser = useMutation(api.users.store); but i get this type error. the function still runs and works as expected. Property 'users' does not exist on type '{ clerkBackend: { AdminGetUserList: FunctionReference<"action", "public", {}, { id: string; email: string | undefined; firstName: string | null; }[] | null>; }; messageActions: { ...; }; }' it cannot find the api because i have replaced... // import { mutation, query } from './_generated/server'; with import { mutation } from './functions'; this is part of my functions.ts
import { entsTableFactory } from 'convex-ents';
import {
customCtx,
customMutation,
customQuery
} from 'convex-helpers/server/customFunctions';
import { GenericDatabaseReader, GenericDatabaseWriter } from 'convex/server';
import { DataModel } from './_generated/dataModel';
import {
internalMutation as baseInternalMutation,
internalQuery as baseInternalQuery,
mutation as baseMutation,
query as baseQuery
} from './_generated/server';
import { entDefinitions } from './schema';
import { MutationCtx } from './types';

type LegacyTables = 'messages' | 'emailList' | 'users';

export const query = customQuery(
baseQuery,
customCtx(async (ctx) => {
return {
table: entsTableFactory(ctx, entDefinitions),
db: ctx.db as unknown as GenericDatabaseReader<
Pick<DataModel, LegacyTables>
>
};
})
);

export const mutation = customMutation(
baseMutation,
customCtx(async (ctx) => {
return {
table: entsTableFactory(ctx, entDefinitions),
db: ctx.db as GenericDatabaseWriter<Pick<DataModel, LegacyTables>>
};
})
);
import { entsTableFactory } from 'convex-ents';
import {
customCtx,
customMutation,
customQuery
} from 'convex-helpers/server/customFunctions';
import { GenericDatabaseReader, GenericDatabaseWriter } from 'convex/server';
import { DataModel } from './_generated/dataModel';
import {
internalMutation as baseInternalMutation,
internalQuery as baseInternalQuery,
mutation as baseMutation,
query as baseQuery
} from './_generated/server';
import { entDefinitions } from './schema';
import { MutationCtx } from './types';

type LegacyTables = 'messages' | 'emailList' | 'users';

export const query = customQuery(
baseQuery,
customCtx(async (ctx) => {
return {
table: entsTableFactory(ctx, entDefinitions),
db: ctx.db as unknown as GenericDatabaseReader<
Pick<DataModel, LegacyTables>
>
};
})
);

export const mutation = customMutation(
baseMutation,
customCtx(async (ctx) => {
return {
table: entsTableFactory(ctx, entDefinitions),
db: ctx.db as GenericDatabaseWriter<Pick<DataModel, LegacyTables>>
};
})
);
how can i fix this? As soon as put this back in
`// import { mutation, query } from './_generated/server';
`// import { mutation, query } from './_generated/server';
` everything works.
14 Replies
Son
SonOP7mo ago
when i look at api.d.ts i can see the types are correct, and users is available. the only object tables available are "clerkbackend and messageActions" as they both use the generated, query and mutation sand not the custom ones.
declare const fullApi: ApiFromModules<{
clerkBackend: typeof clerkBackend;
emailList: typeof emailList;
functions: typeof functions;
messageActions: typeof messageActions;
messages: typeof messages;
migrations: typeof migrations;
types: typeof types;
users: typeof users;
}>;
export declare const api: FilterApi<
typeof fullApi,
FunctionReference<any, "public">
>;
export declare const internal: FilterApi<
typeof fullApi,
FunctionReference<any, "internal">
>;

~
declare const fullApi: ApiFromModules<{
clerkBackend: typeof clerkBackend;
emailList: typeof emailList;
functions: typeof functions;
messageActions: typeof messageActions;
messages: typeof messages;
migrations: typeof migrations;
types: typeof types;
users: typeof users;
}>;
export declare const api: FilterApi<
typeof fullApi,
FunctionReference<any, "public">
>;
export declare const internal: FilterApi<
typeof fullApi,
FunctionReference<any, "internal">
>;

~
Son
SonOP7mo ago
No description
lee
lee7mo ago
Interesting, I don't see anything wrong with your code. You said it works at runtime, which I would assume means npx convex dev is pushing the code and not hitting type errors. So is the type error showing up in your editor or from something like npm run build? My first step would be to restart typescript and eslint in your editor, or just restart the editor.
Michal Srb
Michal Srb7mo ago
I also don't see anything wrong, but it suggests that the custom mutation constructor you build with convex-helpers is not returning the correct type, so it doesn't show up in the API object. Could you share a repo with a repro?
Son
SonOP7mo ago
GitHub
convex-ents-issue/apps/mobile/src/hooks/useStoreUserEffect.ts at ma...
Contribute to AyoCodess/convex-ents-issue development by creating an account on GitHub.
GitHub
convex-ents-issue/packages/api at main · AyoCodess/convex-ents-issue
Contribute to AyoCodess/convex-ents-issue development by creating an account on GitHub.
GitHub
convex-ents-issue/packages/api/core.ts at main · AyoCodess/convex-e...
Contribute to AyoCodess/convex-ents-issue development by creating an account on GitHub.
Son
SonOP7mo ago
the issue is coming frin tge functions.ts
//import { mutation } from './functions';
import { mutation, query } from './_generated/server';
//import { mutation } from './functions';
import { mutation, query } from './_generated/server';
it cant use the custom mutation user.ts
import { mutation } from './functions'; //does not work this causes the issue
**// import { mutation, query } from './_generated/server';**
import dayjs from 'dayjs';

/**
* Insert or update the user in a Convex table then return the document's ID.
*
* The `UserIdentity.tokenIdentifier` string is a stable and unique value we use
* to look up identities.
*
* Keep in mind that `UserIdentity` has a number of optional fields, the
* presence of which depends on the identity provider chosen. It's up to the
* application developer to determine which ones are available and to decide
* which of those need to be persisted. For Clerk the fields are determined
* by the JWT token's Claims config.
*/
export const store = mutation({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error('Called storeUser without authentication present');
}

˜
import { mutation } from './functions'; //does not work this causes the issue
**// import { mutation, query } from './_generated/server';**
import dayjs from 'dayjs';

/**
* Insert or update the user in a Convex table then return the document's ID.
*
* The `UserIdentity.tokenIdentifier` string is a stable and unique value we use
* to look up identities.
*
* Keep in mind that `UserIdentity` has a number of optional fields, the
* presence of which depends on the identity provider chosen. It's up to the
* application developer to determine which ones are available and to decide
* which of those need to be persisted. For Clerk the fields are determined
* by the JWT token's Claims config.
*/
export const store = mutation({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error('Called storeUser without authentication present');
}

˜
`
Michal Srb
Michal Srb7mo ago
@Son I'm seeing a ton of errors when I run npx tsc (both from project directory and from the mobile directory). I assume that this is caused by some of your tsconfig.json config values. Try to get a clean npx tsc first.
Son
SonOP7mo ago
this is probably because i stripped down the project down apologies. i think i worked it out... My user table is using the standard convex mutation, as typed here in my functions.ts. I've classed users to be typed as a legacy table. is that correct?
type LegacyTables = 'messages' | 'emailList' | 'users';
type LegacyTables = 'messages' | 'emailList' | 'users';
Where i think i went wrong was in the user.ts file i had swapped out the standard mutation function for the custom mutation n from fucntion.ts when it fact the code for the store mutation is not using the custom mutation at all right now.
export const store = mutation({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error('Called storeUser without authentication present');
}

//* Check if we've already stored this identity before.

const user = await ctx.db
.query('users')
.withIndex('byToken', (q) =>
q.eq('tokenIdentifier', identity.tokenIdentifier)
)
.unique();

etc...
export const store = mutation({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error('Called storeUser without authentication present');
}

//* Check if we've already stored this identity before.

const user = await ctx.db
.query('users')
.withIndex('byToken', (q) =>
q.eq('tokenIdentifier', identity.tokenIdentifier)
)
.unique();

etc...
`` so am i right in saying, the users.ts store function was using the custom mutation when it fact it should of used the standard mutation function as the "users" table it's marked as a legacy And when i finally covert the store function to use the custom mutation function, then i will no longer get the type error when i bring back in the code below?
import { mutation } from './functions';
import { mutation } from './functions';
instead of...
import { mutation, query } from './_generated/server';
import { mutation, query } from './_generated/server';
Son
SonOP7mo ago
my theory was incorrect i created a new file called relationship.ts with a function that used the custom mutation
import { mutation } from './functions';

export const test = mutation({
args: {},
handler: async (ctx) => {
return '500';
}
});
import { mutation } from './functions';

export const test = mutation({
args: {},
handler: async (ctx) => {
return '500';
}
});
`` it works at run time i get the result '500'' but no types
No description
Son
SonOP7mo ago
i updated the code and removed most of the typescript errors, whats left is related to comvex
import { useConvexAuth, api, useMutation } from '@repo/api/core';
import { useRouter } from 'next/navigation';
import React, { use } from 'react';

const test = useMutation(api.relationships.test);
import { useConvexAuth, api, useMutation } from '@repo/api/core';
import { useRouter } from 'next/navigation';
import React, { use } from 'react';

const test = useMutation(api.relationships.test);
` this had no issues in my next js app so its to do with expo, same imports
Michal Srb
Michal Srb7mo ago
I'm still getting a bunch of errors (even when I revert to import { mutation, query } from "./_generated/server";) . Try to clone the repo, npm install, and then run npx tsc.
Son
SonOP7mo ago
removed all non-related convex type errors. new repo https://github.com/AyoCodess/hm-demo-2
GitHub
GitHub - AyoCodess/hm-demo-2
Contribute to AyoCodess/hm-demo-2 development by creating an account on GitHub.
Son
SonOP7mo ago
found the issue. the baseConfig line 1 "extends": "@repo/typescript-config/base.json",
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"jsx": "react-native",
"types": ["nativewind/types", "node"]
},
"typeRoots": ["./types/*"],
"include": [
"src",
"*.ts",
"index.tsx",
"*.js",
"env.d.ts",
".expo/types/**/*.ts",
"expo-env.d.ts",
"scripts/copy-design-system.js",
"scripts/incrementMinorVersion.js",
"scripts/incrementUpdateVersion.js"
],
"exclude": ["node_modules"]
}
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"jsx": "react-native",
"types": ["nativewind/types", "node"]
},
"typeRoots": ["./types/*"],
"include": [
"src",
"*.ts",
"index.tsx",
"*.js",
"env.d.ts",
".expo/types/**/*.ts",
"expo-env.d.ts",
"scripts/copy-design-system.js",
"scripts/incrementMinorVersion.js",
"scripts/incrementUpdateVersion.js"
],
"exclude": ["node_modules"]
}
i commented out the base config which is below
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"incremental": false,
"isolatedModules": true,
"lib": ["es2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleDetection": "force",
"moduleResolution": "NodeNext",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
}
}
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"incremental": false,
"isolatedModules": true,
"lib": ["es2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleDetection": "force",
"moduleResolution": "NodeNext",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
}
}
` types work as expected now. unbelievable.
Michal Srb
Michal Srb7mo ago
My guess would be that it is "module": "NodeNext", breaking things.

Did you find this page helpful?