struggling with mutation
I'm trying to infer a type from my schema in order to type locationData which I will pass to the mutation but I keep getting the following error:
src/convex/addCompleteProperty.ts|7 col 23-62 error| Type 'TableDefinition<VObject<{ district?: string | undefined; city?: string | undefined; area?: string | undefined; postal_code?: string | undefined; nearest_landmark?: string | undefined; latitude?: number | undefined; longitude?: number | undefined; country: string; province: string; address: string; }, { ...; }, "requ...' does not satisfy the constraint 'Validator<any, OptionalProperty, any>'.Here's the relevant section of my schema Where did I go wrong?
145 Replies
Thanks for posting in <#1088161997662724167>.
Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets.
- Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.)
- Use search.convex.dev to search Docs, Stack, and Discord all at once.
- Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI.
- Avoid tagging staff unless specifically instructed.
Thank you!
@beerman there's one more property access to do
property_locations is the whole table, .validator is the validator used to create the table. You could create a type that did it this way like
type InferFromTable<T> = Infer<T["validator"]>
, this was just a more consistent way to treat validators for Infer<>
since it also works with argument and output validatorsI see, yeah that got rid of the error. What's the optimal way to now let Convex know that locationData is of type Location?
as Location
produced another error
this worked but I was wondering if I can attach the type directlythis worked but I was wondering if I can attach the type directlycould you say more? I'm not sure what attach the type directly means here
I meant, perhaps add the type where declaring locationData in the second async argument
This works though. Last question - say I want to add by_city and by_area to the indexing, do I just chain another
.withIndex
or can I add them all in the same one?are you saying you wish TypeScript syntax were nicer, and let you do
instead of
or
? If so I agree, but that's the syntax we're stuck with by using TypeScript
Yep, I thought so
You can add them to the same one, as long as you access them in that order
Can you elaborate on what you mean by accessing them in that order?
My understanding is that this indexing will allow my queries to execute faster. So i defined that these properties should be indexed in the db, but in order for that to take place, I also need to specify in the mutation. Is that right?
Hmm, okay so I must have misunderstood how it works. I thought that indexing all fields would have been a good idea since the website gives users the options to filter properties by district, city, neighbourhood, etc so I wanted them all to load faster depending on what the user is filtering by
That sounds true
But when a user starts to compound those filters it gets more interesting
The properties you have listed here don't really need to be compounted, right? You don't have to allow searches where city = seattle AND nearest landmark = "space needle"?
Each index is an ordering of all of your properties. If the index is by city, then they're all like pages of a book in order by city name.
If the index is on city and then closest landmark, then closest landmark is like a tiebreaker for properties in the same city.
Not yet, I was just trying to optimize basic filtering at the start. So if the user wanted to view all properties in the city center, they would get served those faster since i'm indexing the city_area
A query is more efficient if there is a single span of pages of this book that have all the results you want.
Yeah, if you write the query to use that index
I'm still a little confused but I don't want to bother you with stupid questions so I'm going to read the docs and try to get a better understanding of how indexing works
Feel free to keep asking, might be slow on the responses
especially helpful to hear after you've read the docs what is still confusing
there's also this essay on what an index is https://docs.convex.dev/database/indexes/indexes-and-query-perf
Alright, il let you know if I'm still confused after taking all the info in
Actually, it looks like I don't even need to define those indexes in the mutation since I already defined what needs to be indexed in my schema and i'm not concerned with speed while checking if a record exists while adding data to the db
Hey there, quick question. I am trying to add complete data for a property and its relations at once. The problem is that a property has fields like property_location_id which can't be passed into the mutation in the propertyData as the records are not yet created.
Here is a part of my schema:
I thought that the best way to handle this would be to infer the property type from my schema and omit those fields.
I tried the following:
But It is now complaining that PropertyData refers to a type but is being used as a value because it wants me to give it a validator, but the validator includes these fields which I don't yet have values for since the records are not yet created. What is the optimal way to do this?
You can also pick fields out of a validator instead of picking them out of the type, like
or you can build these up separately and compose them in the schema
I think your approach sounds good, if you want an endpoint that accepts all this data at once then you'll need to accept these entities in a slightly different form than you store them
Another option is using your own foreign key columns instead of
_id
if you already know those ahead of time
but what you've got here looks good, you'll insert on piece first and then use its id as the foreign key on the other entities
composing validators looks like
Interesting. That was pretty much what I needed. But here is another problem which I'm not sure how to tackle - so essentially, I have a table for
property_type_options
which contains all the different types of properties. The propertyTypeMap
is just an object with the type names and their respective record ids (not sure if there is any way to do this without having this map here). Since I need to link a property type to the property, I'm passing it separately outside propertyData but I want to make sure that the string passed matches a valid property type option. I'm not sure how to achieve this with the validator
bare in mind that i'm trying to minimize maintenance overhead in case schema changes. Without having the map there, I imagine the only other way would be to have some kind of constraint on that field?The propertyTypeMap is just an object with the type names and their respective record ids (not sure if there is any way to do this without having this map here)You could do a db lookup to get the one you want by name, or you could use a literal like
v.union(v.literal('duplex'), v.literal('apartment'), ...)
. The problem with this is that it'll be some trouble to make sure that in dev and prod you have the same IDs for these records. For this reason I'd basically never hardcode IDs, I want my codebase to work even if I cleared the data.
You can do efficient lookups by any column that has an index, not just ID
Without having the map there, I imagine the only other way would be to have some kind of constraint on that fieldNot quite sure I follow, but the validator language seems descriptive enough to do this (it's
v.id("property_type")
it's not descriptive enough to do everything, sometimes you'll still need to write TypeScript code that checks your own invariants.
yeah passing up one of these 15 strings and then writing logic to make sure they match makes sense
the v.string() etc. validator language is really just for the very low-level constraints that makes sense to express in the database and in any programming language
If you want your mutation to accept data that isn't an ID, you can either validate it as a union of literals or just as a string, that you then run arbitrary code on and reject if it's not badI tried the followinf=g but got an error saying that pluck does not exist on type QueryInitializer
If you use ai-generated code it's unfortunately going to make up methods like this
the error message is correct, pluck does not exist on type QueryInitializer
Ouch, I thought that Kappa was trained on your docs
unfortunately before that it was trained on the internet
there's no select equivalent in Convex today, you'll have to grab the whole record
Yeah I was reading through the relevant documentation and couldn't find how to extract one field from a query
this should do it right?
i should probably query it withIndex by_name as well
for property type that looks reasonable, yeah was going to say that
but like if you only have 10 it's not that big a deal
but you'll use less bandwidth if you can just pick the right one
instead of doing a full table scan like would happen as written
Hey Tom, quick question
This gives me the following error. I also tried to use '_id' as the index, which also yields the same error.
Arguments for the rest parameter 'fieldArg' were not provided. src/convex/properties.ts:20:36 - error TS2554: Expected 5 arguments, but got 4. 20 const propertyLocation = await getOneFrom(~~node_modules/convex-helpers/server/relationships.d.ts:59:310 59 export declare function getOneFrom<DataModel extends GenericDataModel, TableName extends TablesWithLookups<DataModel>, IndexName extends UserIndexes<DataModel, TableName>>(db: GenericDatabaseReader<DataModel>, table: TableName, index: IndexName, value: TypeOfFirstIndexField<DataModel, TableName, IndexName>, ...fieldArg: FieldIfDoesntMatchIndex<DataModel, TableName, IndexName>): Promise<DocumentByName<DataModel, TableName> | null>;~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'fieldArg' were not provided. Found 2 errors in the same file, starting at: src/convex/properties.ts:14
Does this error make sense? It says that in
you're missing an argument
@beerman do you have a question about this?
I know that it says that i'm missing an argument, but this argument is optional. I found out that index is literally index, not a field. So that's not useful in my case as I am only indexing property types by name, with each property being given the property type id
so it's literally impossible to index the id as that table doesn't store those ids
Kapa suggested that this is the proper way to get these relations, but that doesn't work either
This argument is only optional if the index is named after the field
Right, but there is no index for this and index is not an optional field
ah got it, the index on id is automatic and doesn't have a name?
What is it you're trying to do, just get by id?
No no, take a look at the schema above.
property_type_options
doesn't contain the id of the property record. It only has its internal id and the property type name
The relevant property_type_id is stored on the property table
so I'm just trying to use it to get that property type relationsorry could you repeat what the goal is?
property has a field called
property_type_id
which stores the id of the propertyType
record in the property_type_options
table. I'm trying to get that related data while querying the property
so instead of getting the record id, i want to get the actual data of the recordok, and you could do this manually with ctx.db.get(property_type_id)? but you'd like to use this helper which combines the property with the proptery type?
question marks because I'm asking, not because this doesn't make sense
ctx.db.get didn't work, i got this error
but yes, I was trying to use the helpers because it's neater imo
What does that error say
I don't understand this error
it's ugly, but it says this is a promise
you need to await the ctx.db.get
ah, i should await it
yep, I was being dumb
Got it, so the ask here is how to get a foreign key when it's a simple id instead of based on a another column
makes sense
so as for the helpers, is there no way to use them without an index?
I don't see a helper for this, seems like it would just be called get?
I think I just misunderstood what these helpers were supposed to be used for. Yes, get works as long as i pass it the id of the record I want to pull. But i don't think that would work for one to many or many to many relations
if there's a signature for the function you'd want would be helpful to see
got it, you'd like something that assembled the object by combining these objects like a join? and these don't do that?
I have
features
which contains all the different features a property could have. And another table called property_features
which contains the id of the property and the id of the feature. So each property could have multiple features. How would I get all the features for a property when i'm not indexing that manually?
I mean, get worked. I got the data i needed.
I just wanted to use these helpers because they're neat. The issue is that like i said, I am not indexing the ids on the property_type_options
Interesting. Looks like ids are indexed by the system by default. I got the data
ah exactly, cool
another question if you don't mind - so since property_type_options, features, amenities, and views are tables that already contain all the predefined options that can be used, is there a good way to implement checking when filling in these fields?
since i'm going to be passing an array of feature strings into this mutation, is it possible to implement type checking on the contents of the array so that all entries are valid features without having to query the db before inserting each feature?
if i wanted to hardcode, I would do something like this and
a
would show as invalid
What's your schema like, do you already have a validator that is the union of these literals
not at a computer atm, but you can use Infer<> on a validator to build the type
or you can map over an array of these literals to build the validator and build the type
The property_type_options, features, amenities, views were prefilled in their repsective tables. Maybe that wasn't the best idea?
the validator would just validate against the name (string) field of each of those tables though, not the actual values right?
Ah if there's no validator for then you need to generate code from these values
I don't quite understand what you want here
The validator for the property_type_options just validates that the input for name is a string. It doesn't contain the possible values that are in the table
You want a type for feature, which is just a union of string literals yeah?
ah yeah you'd need to generate typescript code if you want to represent this in TypeScript
Sorry but I don't quite understand what you mean by that. Generate it from what?
TypeScript isn't going to query your database everytime you typecheck right
yes
so you need to query your database and generate this TypeScript code
or change this to write a validator (or just a typescript type) with all of these
"generate" meaning construct a string from the database values, this isn't a pretty somution
What is the best solution in your opinion?
You clearly know all the best practices and it seems like i'm doing something outside the norm
If these features are static, I'd stick that in a validator
or at least a typescript type
if you need typechecking on something you have to put it in typescript
right, so instead of sticking all these predefined values into tables, just stick them in a validator instead
which would result in less tables, which is a good thing
yeah, and you can still use records for them if you want! But the validator for that table is the union of all these literals
less tables is whatever, use as many tables as you like
this would still introduce some maintenance overhead right since i would then need to edit the validator every time a new option was added to or removed from the table
totally, that's just the normal typescript tradeoff
typescript can't typecheck things that aren't in the code ase
if it's really dynamic, then uou could give up on having a typescript type on these
I understand that, I just throught that convex might have some way to pull from the db to build a dynamic validator or something like that
uou wouldn't expect typescript sutocompletion on eg username
so you can totally dynamically validate it
validation is about runtime
but if you want autocompletion in vscode, that's a typescript thing
ideally, I would also like to get that kind of validation in the convex function dashboard as well
i don't know, it seemed doable
say more, like in the data editor?
yes
it already knows that it wants an array of strings, so why not an array of specific strings?
here
i think I see what you mean, like in Airtable this would work, but in Convex the dashboard interaction is determined by the schema.
and the autocompletion here is TypeScript-based, like in your editor
here's the thing, i get my property listings from a couple different sources i am partnered with. Different agencies in my area, all using their own schemas and listing formats. So normalizing all this data is a pain in the backside
hence, i need everything validated to make sure that it's all congruent
to back up, you can validate this! You just can't autocomplete it with typescript
oh i don't care about the autocompletion
oh sorry, big confusion
validation is about writing javascript code to chevk donething
convex mutations are arbitrary javascript
just check the database for a record with this string
that's what i've been doing, but i didn't even want to let it fail incase there's a wrong string in the array
that's why i wanted to get type checking on the array i'm passing into the mutation
sorry what do you mean by type chevking here
line 18,17
i'm going to be passing arrays of features, amenities, views
if i wanted to hardcode, I would do something like this and
a
would show as invalid
but i don't want to hardcode this. I want to use the values from the features, amenities, views table
these are all predefinedsee that's typescript, you're using types
do you want the kind of chevking where it throws an error at runtime, or the kind where you get red squiggles in your editor?
red squiggles in the editor and convex function input would suffice since i only need it to tell me that one or more of the values provided are invalid
v.string() convex validators do both
yes but that only checks that it's a string
not that i'm passing it an invalid feature
so red squiggles are a typescript thing, you'd need to write out the types or write a validator
ok, and with the validator, those values would still need to be hardcoded
Right, Yeah if you want more runtime checking, you do what you're doing below, checking the db
convex validators are static, a string or a union of strings or the right kind of Id
if i was to delete the features table and just write a validator, i would then have to create a duplicate of the same feature across all the different properties that have it instead of just reusing the same record and linking to it in the property_features table. this is probably a lot less efficient right?
I don't understand this
features table contains all possible features
property_features table contains records of all features (property_id, feature_id) for all properties
I don't know, it might work out the same.
I don't get it, could you show a code example?
say i scrap the features table and just insert property_id, feature into property_features, is that in any way less efficient?
it would make it one to many instead of many to many
Could you show these schemas? I'm confused about what property_id, feature means
oh ok you're building a join table?
Doesn't seem necessary if it's one to many
hey, here is the features example
So here's the dilemma: should I continue doing it this way and add a custom validator to validate all the values of the passed array, or perhaps add the custom validator and ditch the
features
table in favor of simply adding features (property_id, name) into property_features
how would you with all your knowledge and experience personally handle all this?this is nice
I'd do it this way, and you can still have a table of these if you want, indexed by that string
Alrighty, one more thing. Do you know how to use
TableNamesInDataModel
? it's a generic and expects a parameter, I assume my dataModel from generated/dataModel.d.ts
?
EDIT: I think rather than TableNamesInDataModel
, TableNames
from _generated/dataModel
should work.
EDIT2: No, now my index names are not being correctly inferred "Argument of type "by_name" is not assignable to parameter of type "by_id" | "by_creation_date"
TableNamesInDataModel<DataModel>
cannot use namespace 'TableNamesInDataModel' as a typeHow did you import that type?
import TableNamesInDataModel from 'convex/server';
import DataModel from './_generated/dataModel';
that's not valid import syntax
well it doesn't do what you want
you want to use curly braces
i did try that, as well as importing them as types. Here's with curly braces though
Are you using an IDE / vscode? it can be more reliable to auto-import
neovim, i do have auto imports enabled. I just didn't accept as i was trying different things
Did you fix both if these imports?
looks like it's defaulting to system indexes rather than picking up on the user defined indexes per table
yep
Can you look at these types?
In neovim i write
const foo: never = DataModel
and look at the error
it is there
not the source code, the hover type
you might try vscode if you don't have your neivim typescript set up yet
I have it set up
you want to be able to expand a
type
I'm wondering what that type is, maybe it's broken somewhere
I think the error might be happening because i'm trying withIndex on table even though some tables don't have by_name set up as an index
oh sorry, there it is
harmless but still annoying
obviously i'm not going to pass in a table that doesn't have that index
I don't understand this
by name isn't an index thing, it's just tables having names
I'm going offline for a bit, I'd look for TypeScript errors
it has to be
this helper is meant to replace all these getTypeId helpers
oh sorry, yeah that makes sense
yeah you have e some generics to wrangle here
should be resolved if I also pass index as an argument instead of specifying in the query
yeah maybe, these are tricky
ok signing off
I'm just going to
// @ts-ignore
that one as it's a waste of timethe generic is easier if you pass in the table instead of passing in the table name
I couldn't get it to work
can you show me what you mean?
For this you can't accept
TableNamesInDataModel<DataModel>
with TypeScript because some of those tables might not have a .name column or a .by_name index, as you pointed out. So you'd want to write a function that took in a subset of DataModel containing just the tables that do fit those constraints (this is a fancy mapped type you could write) and then type your ctx with that. This is pretty fancy, I wouldn't bother. I'd write separate getRecordId functions for each table you need it for.Yep, I came to the same conclusion and stuck with a separate function for each type of record as needed
I stumbled into a problem where some properties would have additions like a pool with water jets and waterfalls that didn't exactly match the general "Private Pool" predefined feature in my db and validator, and adding all these variations of features every time one popped up is a mess and a nightmare. So I added a "details" field to account for these variations on a per-property level while still fitting into my general predefined feature and amenity options.
I ended up with something a little more complex than i'd have liked (in terms of having to add a new feature or amenity to both the db table as well as the validator - il probably write a helper function that accepts a feature and adds it to the features table and then appends it to the validator stored in convex/validators or something like that to make this simpler), but I didn't see a simpler way to do it.
Below are the are the relevant extracts you need to get an idea. Let me know what you think
schema
mutation
For somebody like me who had only used Supabase in most of my projects and played around with PocketBase on a few occasions, Convex is magical and such a pleasure to work with.
@ballingt sorry for another ping,
As you know, I have a table
features
which stores different feature options and joiner table property_features
which connects the features to properties and has an additional details
field for property specific information per feature.
I'm trying to get all the property features (name, as well as the details), so I thought that getManyVia would be the best option to use, but I'm only getting the names but not the details. Am I not using the right helper?
extract from schema
query
I know that I can do it like this but is it optimal?