diff --git a/src/select-query-parser.ts b/src/select-query-parser.ts index 5c682117..54f8f317 100644 --- a/src/select-query-parser.ts +++ b/src/select-query-parser.ts @@ -129,54 +129,91 @@ type EatWhitespace = string extends Input : Input /** - * Returns a boolean representing whether there is a foreign key with the given name. + * Check if Item is in Array */ -type HasFKey = Relationships extends [infer R] - ? R extends { foreignKeyName: FKeyName } +type InArray = Array extends [infer I] + ? I extends Item ? true : false - : Relationships extends [infer R, ...infer Rest] - ? HasFKey extends true + : Array extends [infer I, ...infer Rest] + ? InArray extends true ? true - : HasFKey + : InArray : false +/** + * Returns a boolean representing whether there is a foreign key with the given name. + */ +type HasFKey = InArray<{ foreignKeyName: FKeyName }, Relationships> /** * Returns a boolean representing whether there the foreign key has a unique constraint. */ -type HasUniqueFKey = Relationships extends [infer R] - ? R extends { foreignKeyName: FKeyName; isOneToOne: true } - ? true - : false +type HasUniqueFKey = InArray< + { foreignKeyName: FKeyName; isOneToOne: true }, + Relationships +> + +/** + * Returns the relation referenced by this column name, if such relation exists + */ +type ColumnForeignRelation = Relationships extends [infer R] + ? R extends { columns: string[]; referencedRelation: string } + ? InArray extends true + ? [R['referencedRelation']] + : null + : null : Relationships extends [infer R, ...infer Rest] - ? HasUniqueFKey extends true - ? true - : HasUniqueFKey - : false + ? ColumnForeignRelation extends [infer Rel] + ? [Rel] + : ColumnForeignRelation + : null /** * Returns a boolean representing whether there is a foreign key referencing * a given relation. */ -type HasFKeyToFRel = Relationships extends [infer R] - ? R extends { referencedRelation: FRelName } - ? true - : false - : Relationships extends [infer R, ...infer Rest] - ? HasFKeyToFRel extends true - ? true - : HasFKeyToFRel - : false +type HasFKeyToFRel = InArray< + { referencedRelation: FRelName }, + Relationships +> -type HasUniqueFKeyToFRel = Relationships extends [infer R] - ? R extends { referencedRelation: FRelName; isOneToOne: true } - ? true - : false - : Relationships extends [infer R, ...infer Rest] - ? HasUniqueFKeyToFRel extends true - ? true - : HasUniqueFKeyToFRel - : false +type HasUniqueFKeyToFRel = InArray< + { referencedRelation: FRelName; isOneToOne: true }, + Relationships +> + +type FieldRelation< + Schema extends GenericSchema, + Field extends { name: string; children: unknown[] }, + FRel extends keyof (Schema['Tables'] & Schema['Views']), + RelationName, + Relationships +> = { + [_ in Field['name']]: GetResultHelper< + Schema, + (Schema['Tables'] & Schema['Views'])[FRel]['Row'], + FRel, + (Schema['Tables'] & Schema['Views'])[FRel] extends { Relationships: infer R } ? R : unknown, + Field['children'], + unknown + > extends infer Child + ? // One-to-one relationship - referencing column(s) has unique/pkey constraint. + HasUniqueFKeyToFRel< + RelationName, + (Schema['Tables'] & Schema['Views'])[FRel] extends { + Relationships: infer R + } + ? R + : unknown + > extends true + ? Child | null + : Relationships extends unknown[] + ? HasFKeyToFRel extends true + ? Child | null + : Child[] + : Child[] + : never +} /** * Constructs a type definition for a single field of an object. @@ -237,34 +274,13 @@ type ConstructFieldDefinition< : never } : Field extends { name: string; original: string; children: unknown[] } - ? { - [_ in Field['name']]: GetResultHelper< - Schema, - (Schema['Tables'] & Schema['Views'])[Field['original']]['Row'], - Field['original'], - (Schema['Tables'] & Schema['Views'])[Field['original']] extends { Relationships: infer R } - ? R - : unknown, - Field['children'], - unknown - > extends infer Child - ? // One-to-one relationship - referencing column(s) has unique/pkey constraint. - HasUniqueFKeyToFRel< - RelationName, - (Schema['Tables'] & Schema['Views'])[Field['original']] extends { - Relationships: infer R - } - ? R - : unknown - > extends true - ? Child | null - : Relationships extends unknown[] - ? HasFKeyToFRel extends true - ? Child | null - : Child[] - : Child[] - : never - } + ? ColumnForeignRelation extends [infer ForeignRel] + ? // handle `col:foreign_key_column` + ForeignRel extends keyof (Schema['Tables'] & Schema['Views']) + ? FieldRelation + : SelectQueryError<`Unknown relation in a relationship`> + : // handle `col:relation` + FieldRelation : Field extends { name: string; type: infer T } ? { [K in Field['name']]: T } : Field extends { name: string; original: string } diff --git a/test/index.test-d.ts b/test/index.test-d.ts index b9c24c16..17adf0b2 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -149,6 +149,30 @@ const postgrest = new PostgrestClient(REST_URL) expectType(message.user) } +// many-to-one relationship (fkey) +{ + const { data: message, error } = await postgrest + .from('messages') + .select('user:users!messages_username_fkey(*)') + .single() + if (error) { + throw new Error(error.message) + } + expectType(message.user) +} + +// many-to-one relationship (column name) +{ + const { data: message, error } = await postgrest + .from('messages') + .select('user:username(*)') + .single() + if (error) { + throw new Error(error.message) + } + expectType(message.user) +} + // one-to-many relationship { const { data: user, error } = await postgrest.from('users').select('messages(*)').single()