import { Ast } from './parser' import { AggregateFunctions, ContainsNull, GenericRelationship, GenericSchema, GenericTable, IsNonEmptyArray, TablesAndViews, UnionToArray, GenericFunction, GenericSetofOption, } from './types' export type IsAny = 0 extends 1 & T ? true : false export type SelectQueryError = { error: true } & Message /* ** Because of pg-meta types generation there is some cases where a same relationship can be duplicated ** if the relation is across schemas and views this ensure that we dedup those relations and treat them ** as postgrest would. ** This is no longer the case and has been patched here: https://github.com/supabase/postgres-meta/pull/809 ** But we still need this for retro-compatibilty with older generated types ** TODO: Remove this in next major version */ export type DeduplicateRelationships = T extends readonly [ infer First, ...infer Rest, ] ? First extends Rest[number] ? DeduplicateRelationships : [First, ...DeduplicateRelationships] : T export type GetFieldNodeResultName = Field['alias'] extends string ? Field['alias'] : Field['aggregateFunction'] extends AggregateFunctions ? Field['aggregateFunction'] : Field['name'] type FilterRelationNodes = UnionToArray< { [K in keyof Nodes]: Nodes[K] extends Ast.SpreadNode ? Nodes[K]['target'] : Nodes[K] extends Ast.FieldNode ? IsNonEmptyArray extends true ? Nodes[K] : never : never }[number] > type ResolveRelationships< Schema extends GenericSchema, RelationName extends string, Relationships extends GenericRelationship[], Nodes extends Ast.FieldNode[], > = UnionToArray<{ [K in keyof Nodes]: Nodes[K] extends Ast.FieldNode ? ResolveRelationship extends infer Relation ? Relation extends { relation: { referencedRelation: string foreignKeyName: string match: string } from: string } ? { referencedTable: Relation['relation']['referencedRelation'] fkName: Relation['relation']['foreignKeyName'] from: Relation['from'] match: Relation['relation']['match'] fieldName: GetFieldNodeResultName } : Relation : never : never }>[0] /** * Checks if a relation is implicitly referenced twice, requiring disambiguation */ type IsDoubleReference = T extends { referencedTable: infer RT fieldName: infer FN match: infer M } ? M extends 'col' | 'refrel' ? U extends { referencedTable: RT; fieldName: FN; match: M } ? true : false : false : false /** * Compares one element with all other elements in the array to find duplicates */ type CheckDuplicates = Arr extends [infer Head, ...infer Tail] ? IsDoubleReference extends true ? Head | CheckDuplicates // Return the Head if duplicate : CheckDuplicates // Otherwise, continue checking : never /** * Iterates over the elements of the array to find duplicates */ type FindDuplicatesWithinDeduplicated = Arr extends [infer Head, ...infer Tail] ? CheckDuplicates | FindDuplicatesWithinDeduplicated : never type FindDuplicates = FindDuplicatesWithinDeduplicated< DeduplicateRelationships > export type CheckDuplicateEmbededReference< Schema extends GenericSchema, RelationName extends string, Relationships extends GenericRelationship[], Nodes extends Ast.Node[], > = FilterRelationNodes extends infer RelationsNodes ? RelationsNodes extends Ast.FieldNode[] ? ResolveRelationships< Schema, RelationName, Relationships, RelationsNodes > extends infer ResolvedRels ? ResolvedRels extends unknown[] ? FindDuplicates extends infer Duplicates ? Duplicates extends never ? false : Duplicates extends { fieldName: infer FieldName } ? FieldName extends string ? { [K in FieldName]: SelectQueryError<`table "${RelationName}" specified more than once use hinting for desambiguation`> } : false : false : false : false : false : false : false /** * 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 /** * Checks if there is more than one relation to a given foreign relation name in the Relationships. */ type HasMultipleFKeysToFRelDeduplicated = Relationships extends [ infer R, ...infer Rest, ] ? R extends { referencedRelation: FRelName } ? HasFKeyToFRel extends true ? true : HasMultipleFKeysToFRelDeduplicated : HasMultipleFKeysToFRelDeduplicated : false type HasMultipleFKeysToFRel< FRelName, Relationships extends unknown[], > = HasMultipleFKeysToFRelDeduplicated> type CheckRelationshipError< Schema extends GenericSchema, Relationships extends GenericRelationship[], CurrentTableOrView extends keyof TablesAndViews & string, FoundRelation, > = FoundRelation extends SelectQueryError ? FoundRelation : // If the relation is a reverse relation with no hint (matching by name) FoundRelation extends { relation: { referencedRelation: infer RelatedRelationName name: string } direction: 'reverse' } ? RelatedRelationName extends string ? // We check if there is possible confusion with other relations with this table HasMultipleFKeysToFRel extends true ? // If there is, postgrest will fail at runtime, and require desambiguation via hinting SelectQueryError<`Could not embed because more than one relationship was found for '${RelatedRelationName}' and '${CurrentTableOrView}' you need to hint the column with ${RelatedRelationName}! ?`> : FoundRelation : never : // Same check for forward relationships, but we must gather the relationships from the found relation FoundRelation extends { relation: { referencedRelation: infer RelatedRelationName name: string } direction: 'forward' from: infer From } ? RelatedRelationName extends string ? From extends keyof TablesAndViews & string ? HasMultipleFKeysToFRel< RelatedRelationName, TablesAndViews[From]['Relationships'] > extends true ? SelectQueryError<`Could not embed because more than one relationship was found for '${From}' and '${RelatedRelationName}' you need to hint the column with ${From}! ?`> : FoundRelation : never : never : FoundRelation /** * Resolves relationships for embedded resources and retrieves the referenced Table */ export type ResolveRelationship< Schema extends GenericSchema, Relationships extends GenericRelationship[], Field extends Ast.FieldNode, CurrentTableOrView extends keyof TablesAndViews & string, > = ResolveReverseRelationship< Schema, Relationships, Field, CurrentTableOrView > extends infer ReverseRelationship ? ReverseRelationship extends false ? CheckRelationshipError< Schema, Relationships, CurrentTableOrView, ResolveForwardRelationship > : CheckRelationshipError : never /** * Resolves reverse relationships (from children to parent) */ type ResolveReverseRelationship< Schema extends GenericSchema, Relationships extends GenericRelationship[], Field extends Ast.FieldNode, CurrentTableOrView extends keyof TablesAndViews & string, > = FindFieldMatchingRelationships extends infer FoundRelation ? FoundRelation extends never ? false : FoundRelation extends { referencedRelation: infer RelatedRelationName } ? RelatedRelationName extends string ? RelatedRelationName extends keyof TablesAndViews ? // If the relation was found via hinting we just return it without any more checks FoundRelation extends { hint: string } ? { referencedTable: TablesAndViews[RelatedRelationName] relation: FoundRelation direction: 'reverse' from: CurrentTableOrView } : // If the relation was found via implicit relation naming, we must ensure there is no conflicting matches HasMultipleFKeysToFRel extends true ? SelectQueryError<`Could not embed because more than one relationship was found for '${RelatedRelationName}' and '${CurrentTableOrView}' you need to hint the column with ${RelatedRelationName}! ?`> : { referencedTable: TablesAndViews[RelatedRelationName] relation: FoundRelation direction: 'reverse' from: CurrentTableOrView } : SelectQueryError<`Relation '${RelatedRelationName}' not found in schema.`> : false : false : false export type FindMatchingTableRelationships< Schema extends GenericSchema, Relationships extends GenericRelationship[], value extends string, > = Relationships extends [infer R, ...infer Rest] ? Rest extends GenericRelationship[] ? R extends { referencedRelation: infer ReferencedRelation } ? ReferencedRelation extends keyof Schema['Tables'] ? R extends { foreignKeyName: value } ? R & { match: 'fkname' } : R extends { referencedRelation: value } ? R & { match: 'refrel' } : R extends { columns: [value] } ? R & { match: 'col' } : FindMatchingTableRelationships : FindMatchingTableRelationships : false : false : false export type FindMatchingViewRelationships< Schema extends GenericSchema, Relationships extends GenericRelationship[], value extends string, > = Relationships extends [infer R, ...infer Rest] ? Rest extends GenericRelationship[] ? R extends { referencedRelation: infer ReferencedRelation } ? ReferencedRelation extends keyof Schema['Views'] ? R extends { foreignKeyName: value } ? R & { match: 'fkname' } : R extends { referencedRelation: value } ? R & { match: 'refrel' } : R extends { columns: [value] } ? R & { match: 'col' } : FindMatchingViewRelationships : FindMatchingViewRelationships : false : false : false export type FindMatchingHintTableRelationships< Schema extends GenericSchema, Relationships extends GenericRelationship[], hint extends string, name extends string, > = Relationships extends [infer R, ...infer Rest] ? Rest extends GenericRelationship[] ? R extends { referencedRelation: infer ReferencedRelation } ? ReferencedRelation extends name ? R extends { foreignKeyName: hint } ? R & { match: 'fkname' } : R extends { referencedRelation: hint } ? R & { match: 'refrel' } : R extends { columns: [hint] } ? R & { match: 'col' } : FindMatchingHintTableRelationships : FindMatchingHintTableRelationships : false : false : false export type FindMatchingHintViewRelationships< Schema extends GenericSchema, Relationships extends GenericRelationship[], hint extends string, name extends string, > = Relationships extends [infer R, ...infer Rest] ? Rest extends GenericRelationship[] ? R extends { referencedRelation: infer ReferencedRelation } ? ReferencedRelation extends name ? R extends { foreignKeyName: hint } ? R & { match: 'fkname' } : R extends { referencedRelation: hint } ? R & { match: 'refrel' } : R extends { columns: [hint] } ? R & { match: 'col' } : FindMatchingHintViewRelationships : FindMatchingHintViewRelationships : false : false : false type IsColumnsNullable< Table extends Pick, Columns extends (keyof Table['Row'])[], > = Columns extends [infer Column, ...infer Rest] ? Column extends keyof Table['Row'] ? ContainsNull extends true ? true : IsColumnsNullable : false : false // Check weither or not a 1-1 relation is nullable by checking against the type of the columns export type IsRelationNullable< Table extends GenericTable, Relation extends GenericRelationship, > = IsColumnsNullable type TableForwardRelationships< Schema extends GenericSchema, TName, > = TName extends keyof TablesAndViews ? UnionToArray< RecursivelyFindRelationships> > extends infer R ? R extends (GenericRelationship & { from: keyof TablesAndViews })[] ? R : [] : [] : [] type RecursivelyFindRelationships< Schema extends GenericSchema, TName, Keys extends keyof TablesAndViews, > = Keys extends infer K ? K extends keyof TablesAndViews ? FilterRelationships[K]['Relationships'], TName, K> extends never ? RecursivelyFindRelationships> : | FilterRelationships[K]['Relationships'], TName, K> | RecursivelyFindRelationships> : false : false type FilterRelationships = R extends readonly (infer Rel)[] ? Rel extends { referencedRelation: TName } ? Rel & { from: From } : never : never export type ResolveForwardRelationship< Schema extends GenericSchema, Field extends Ast.FieldNode, CurrentTableOrView extends keyof TablesAndViews & string, > = FindFieldMatchingRelationships< Schema, TablesAndViews[Field['name']]['Relationships'], Ast.FieldNode & { name: CurrentTableOrView; hint: Field['hint'] } > extends infer FoundByName ? FoundByName extends GenericRelationship ? { referencedTable: TablesAndViews[Field['name']] relation: FoundByName direction: 'forward' from: Field['name'] type: 'found-by-name' } : FindFieldMatchingRelationships< Schema, TableForwardRelationships, Field > extends infer FoundByMatch ? FoundByMatch extends GenericRelationship & { from: keyof TablesAndViews } ? { referencedTable: TablesAndViews[FoundByMatch['from']] relation: FoundByMatch direction: 'forward' from: CurrentTableOrView type: 'found-by-match' } : FindJoinTableRelationship< Schema, CurrentTableOrView, Field['name'] > extends infer FoundByJoinTable ? FoundByJoinTable extends GenericRelationship ? { referencedTable: TablesAndViews[FoundByJoinTable['referencedRelation']] relation: FoundByJoinTable & { match: 'refrel' } direction: 'forward' from: CurrentTableOrView type: 'found-by-join-table' } : ResolveEmbededFunctionJoinTableRelationship< Schema, CurrentTableOrView, Field['name'] > extends infer FoundEmbededFunctionJoinTableRelation ? FoundEmbededFunctionJoinTableRelation extends GenericSetofOption ? { referencedTable: TablesAndViews[FoundEmbededFunctionJoinTableRelation['to']] relation: { foreignKeyName: `${Field['name']}_${CurrentTableOrView}_${FoundEmbededFunctionJoinTableRelation['to']}_forward` columns: [] isOneToOne: FoundEmbededFunctionJoinTableRelation['isOneToOne'] extends true ? true : false referencedColumns: [] referencedRelation: FoundEmbededFunctionJoinTableRelation['to'] } & { match: 'func' isNotNullable: FoundEmbededFunctionJoinTableRelation['isNotNullable'] extends true ? true : FoundEmbededFunctionJoinTableRelation['isSetofReturn'] extends true ? false : true isSetofReturn: FoundEmbededFunctionJoinTableRelation['isSetofReturn'] } direction: 'forward' from: CurrentTableOrView type: 'found-by-embeded-function' } : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> /** * Given a CurrentTableOrView, finds all join tables to this relation. * For example, if products and categories are linked via product_categories table: * * @example * Given: * - CurrentTableView = 'products' * - FieldName = "categories" * * It should return this relationship from product_categories: * { * foreignKeyName: "product_categories_category_id_fkey", * columns: ["category_id"], * isOneToOne: false, * referencedRelation: "categories", * referencedColumns: ["id"] * } */ type ResolveJoinTableRelationship< Schema extends GenericSchema, CurrentTableOrView extends keyof TablesAndViews & string, FieldName extends string, > = { [TableName in keyof TablesAndViews]: DeduplicateRelationships< TablesAndViews[TableName]['Relationships'] > extends readonly (infer Rel)[] ? Rel extends { referencedRelation: CurrentTableOrView } ? DeduplicateRelationships< TablesAndViews[TableName]['Relationships'] > extends readonly (infer OtherRel)[] ? OtherRel extends { referencedRelation: FieldName } ? OtherRel : never : never : never : never }[keyof TablesAndViews] type ResolveEmbededFunctionJoinTableRelationship< Schema extends GenericSchema, CurrentTableOrView extends keyof TablesAndViews & string, FieldName extends string, > = FindMatchingFunctionBySetofFrom< Schema['Functions'][FieldName], CurrentTableOrView > extends infer Fn ? Fn extends GenericFunction ? Fn['SetofOptions'] : false : false export type FindJoinTableRelationship< Schema extends GenericSchema, CurrentTableOrView extends keyof TablesAndViews & string, FieldName extends string, > = ResolveJoinTableRelationship extends infer Result ? [Result] extends [never] ? false : Result : never /** * Finds a matching relationship based on the FieldNode's name and optional hint. */ export type FindFieldMatchingRelationships< Schema extends GenericSchema, Relationships extends GenericRelationship[], Field extends Ast.FieldNode, > = Field extends { hint: string } ? FindMatchingHintTableRelationships< Schema, Relationships, Field['hint'], Field['name'] > extends GenericRelationship ? FindMatchingHintTableRelationships & { branch: 'found-in-table-via-hint' hint: Field['hint'] } : FindMatchingHintViewRelationships< Schema, Relationships, Field['hint'], Field['name'] > extends GenericRelationship ? FindMatchingHintViewRelationships & { branch: 'found-in-view-via-hint' hint: Field['hint'] } : SelectQueryError<'Failed to find matching relation via hint'> : FindMatchingTableRelationships extends GenericRelationship ? FindMatchingTableRelationships & { branch: 'found-in-table-via-name' name: Field['name'] } : FindMatchingViewRelationships< Schema, Relationships, Field['name'] > extends GenericRelationship ? FindMatchingViewRelationships & { branch: 'found-in-view-via-name' name: Field['name'] } : SelectQueryError<'Failed to find matching relation via name'> export type JsonPathToAccessor = Path extends `${infer P1}->${infer P2}` ? P2 extends `>${infer Rest}` // Handle ->> operator ? JsonPathToAccessor<`${P1}.${Rest}`> : P2 extends string // Handle -> operator ? JsonPathToAccessor<`${P1}.${P2}`> : Path : Path extends `>${infer Rest}` // Clean up any remaining > characters ? JsonPathToAccessor : Path extends `${infer P1}::${infer _}` // Handle type casting ? JsonPathToAccessor : Path extends `${infer P1}${')' | ','}${infer _}` // Handle closing parenthesis and comma ? P1 : Path export type JsonPathToType = Path extends '' ? T : ContainsNull extends true ? JsonPathToType, Path> : Path extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? JsonPathToType : never : Path extends keyof T ? T[Path] : never export type IsStringUnion = string extends T ? false : T extends string ? [T] extends [never] ? false : true : false type MatchingFunctionBySetofFrom< Fn extends GenericFunction, TableName extends string, > = Fn['SetofOptions'] extends GenericSetofOption ? TableName extends Fn['SetofOptions']['from'] ? Fn : never : false type FindMatchingFunctionBySetofFrom< FnUnion, TableName extends string, > = FnUnion extends infer Fn extends GenericFunction ? MatchingFunctionBySetofFrom : false type ComputedField< Schema extends GenericSchema, RelationName extends keyof TablesAndViews, FieldName extends keyof TablesAndViews[RelationName]['Row'], > = FieldName extends keyof Schema['Functions'] ? Schema['Functions'][FieldName] extends { Args: { '': TablesAndViews[RelationName]['Row'] } Returns: any } ? FieldName : never : never // Given a relation name (Table or View) extract all the "computed fields" based on the Row // object, and the schema functions definitions export type GetComputedFields< Schema extends GenericSchema, RelationName extends keyof TablesAndViews, > = { [K in keyof TablesAndViews[RelationName]['Row']]: ComputedField }[keyof TablesAndViews[RelationName]['Row']]