Where is join request typing handled?
Where is the typing of join request handled?example when using .select("projects(user_id)")typing is assuming projects being an [] of object.
I'll help you find where the typing of join requests is handled in the supabase-js repository. Let me start by exploring the structure and searching for relevant code.
Let me explore the postgrest-js package more specifically, as it seems to handle the select query parsing:
Now let me look at the select-query-parser result.ts file, which seems to handle the typing of select results:
Let me continue reading the result.ts file to see how join relationships are typed:
Let me continue reading to find the ProcessEmbeddedResource type:
Perfect! Now let me look at the ResolveRelationship utility function to understand how the relationship is determined:
Now let me find the ResolveRelationship function which is the core logic:
Let me try again:
Great! Now let me look at a test file to see concrete examples:
Perfect! Now let me look at one of the test files that specifically tests typing:
Now let me check where the determination of whether something is an array happens. Let me look at the key part in result.ts where it determines if a relation is one-to-one or one-to-many:
Perfect! Now I have a good understanding. Let me check the types.ts file to see how GenericRelationship is defined:
Perfect! Now let me create a comprehensive summary. Let me also check one more test file to show a concrete example:
Let me check the relationships test file for more concrete examples:
Excellent! Now I have all the information I need. Let me provide a comprehensive answer to the user.
##Summary
The typing of join requests (embedded resources) in supabase-js is handled through a sophisticated type system in the postgrest-js package. Here's where and how it works:
###Key Files Involved:
packages/core/postgrest-js/src/select-query-parser/result.ts- Main type inference logicpackages/core/postgrest-js/src/select-query-parser/utils.ts- Relationship resolutionpackages/core/postgrest-js/src/types/common/common.ts- Type definitions for relationships
###How It Works:
####1. Relationship Metadata (GenericRelationship)
When you define your schema, each relationship includes an isOneToOne flag:
export type GenericRelationship = {
foreignKeyName: string
columns: string[]
isOneToOne?: boolean // ← This determines array vs single object
referencedRelation: string
referencedColumns: string[]
}
####2. The Core Type: ProcessEmbeddedResourceResult
The main logic that determines whether a field is typed as an array [] is in result.ts at around line 417-422:
type ProcessEmbeddedResourceResult<...> =
ProcessNodes<...> extends infer ProcessedChildren
? {
[K in GetFieldNodeResultName<Field>]: Resolved['direction'] extends 'forward'
? Field extends { innerJoin: true }
? Resolved['relation']['isOneToOne'] extends true
? ProcessedChildren // ← Single object (1-to-1)
: ProcessedChildren[] // ← Array (1-to-many)
: Resolved['relation']['isOneToOne'] extends true
? ... // Single object
: ProcessedChildren[] // ← Array (1-to-many)
Key Decision Point:
- If
isOneToOne === true: result isProcessedChildren(single object) - If
isOneToOne === falseor undefined: result isProcessedChildren[](array)
####3. Relationship Resolution: ResolveRelationship
The type at line 226 in utils.ts determines what relationship metadata is found for a given field:
export type ResolveRelationship<
Schema extends GenericSchema,
Relationships extends GenericRelationship[],
Field extends Ast.FieldNode,
CurrentTableOrView extends keyof TablesAndViews<Schema> & string,
>
This function:
- Looks up relationships in your schema
- Matches the field name to a relationship
- Returns the complete relationship object including
isOneToOne
###Practical Example:
When you write:
.select("projects(user_id)")
The type system:
- Parses
projectsas a field with children[user_id] - Calls
ResolveRelationshipto find theprojectsrelationship definition - Checks
projects_relationship.isOneToOne:- If
true: types as{ projects: Project } - If
false: types as{ projects: Project[] }← Your case
- If
###What Determines isOneToOne?
The isOneToOne flag in your database schema comes from your Postgres schema generation (typically via postgres-meta):
- One-to-many relationships (foreign key on the "many" side):
isOneToOne: false→ array - One-to-one relationships (unique constraint or primary key):
isOneToOne: true→ single object - Many-to-one relationships (reverse joins): handled differently, often returns single object
###Test Example:
From packages/core/postgrest-js/test/relationships.test.ts:
// One-to-many relationship (messages is one-to-many)
.select('username, messages(id)')
// Types as: { username: string, messages: Message[] }
// One-to-one relationship (user is many-to-one on message)
.select('user:users(*)')
// Types as: { user: User }
The magic happens because the generated schema knows that a messages relationship from users is one-to-many, while a reverse users relationship from messages is one-to-one.
END OF POST
Claude Haiku 4.5