Skip to content

Instantly share code, notes, and snippets.

@bradennapier
Last active May 24, 2023 22:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bradennapier/d7d53b087b214aef93a158840af7e15c to your computer and use it in GitHub Desktop.
Save bradennapier/d7d53b087b214aef93a158840af7e15c to your computer and use it in GitHub Desktop.
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-shadow */
// Credits to @bnjmnt4n (https://www.npmjs.com/package/postgrest-query)
import type { GenericSchema } from '@supabase/supabase-js/dist/module/lib/types';
// https://twitter.com/mattpocockuk/status/1622730173446557697
type Prettify<T> = { [K in keyof T]: T[K] } & {};
type Whitespace = ' ' | '\n' | '\t';
type LowerAlphabet =
| 'a'
| 'b'
| 'c'
| 'd'
| 'e'
| 'f'
| 'g'
| 'h'
| 'i'
| 'j'
| 'k'
| 'l'
| 'm'
| 'n'
| 'o'
| 'p'
| 'q'
| 'r'
| 's'
| 't'
| 'u'
| 'v'
| 'w'
| 'x'
| 'y'
| 'z';
type Alphabet = LowerAlphabet | Uppercase<LowerAlphabet>;
type Digit = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0';
type Letter = Alphabet | Digit | '_';
type Json = string | number | boolean | null | { [key: string]: Json } | Json[];
// /**
// * Parsed node types.
// * Currently only `*` and all other fields.
// */
// type ParsedNode =
// | { star: true }
// | { name: string; original: string }
// | { name: string; foreignTable: true }
// | { name: string; type: T };
/**
* Parser errors.
*/
type ParserError<Message extends string> = { error: true } & Message;
type GenericStringError = ParserError<'Received a generic string'>;
/**
* Trims whitespace from the left of the input.
*/
type EatWhitespace<Input extends string> = string extends Input
? GenericStringError
: Input extends `${Whitespace}${infer Remainder}`
? EatWhitespace<Remainder>
: Input;
type OptFlags = { null?: boolean; single?: boolean; many?: boolean };
type ParseOptFlags<Output, Opts extends OptFlags> = [
Opts['null'] extends false ? never : null,
Opts['many'] extends false ? never : Output[],
Opts['single'] extends false ? never : Output,
][number];
/**
* Constructs a type definition for a single field of an object.
*
* @param Definitions Record of definitions, possibly generated from PostgREST's OpenAPI spec.
* @param Name Name of the table being queried.
* @param Field Single field parsed by `ParseQuery`.
*/
type ConstructFieldDefinition<
Schema extends GenericSchema,
Row extends Record<string, unknown>,
Field,
Opts extends OptFlags = { maybe: false; single: true; many: false },
> = Field extends {
star: true;
}
? Row
: Field extends { name: string; original: string; children: unknown[] }
? {
[_ in Field['name']]: GetResultHelper<
Schema,
(Schema['Tables'] & Schema['Views'])[Field['original']]['Row'],
Field['children'],
Opts,
unknown
> extends infer Child
? ParseOptFlags<Child, Opts>
: never;
}
: Field extends { name: string; original: string }
? { [K in Field['name']]: Row[Field['original']] }
: Field extends { name: string; type: infer T }
? { [K in Field['name']]: T }
: Record<string, unknown>;
/**
* Notes: all `Parse*` types assume that their input strings have their whitespace
* removed. They return tuples of ["Return Value", "Remainder of text"] or
* a `ParserError`.
*/
/**
* Reads a consecutive sequence of more than 1 letter,
* where letters are `[0-9a-zA-Z_]`.
*/
type ReadLetters<Input extends string> = string extends Input
? GenericStringError
: ReadLettersHelper<Input, ''> extends [
`${infer Letters}`,
`${infer Remainder}`,
]
? Letters extends ''
? ParserError<`Expected letter at \`${Input}\``>
: [Letters, Remainder]
: ReadLettersHelper<Input, ''>;
type ReadLettersHelper<
Input extends string,
Acc extends string,
> = string extends Input
? GenericStringError
: Input extends `${infer L}${infer Remainder}`
? L extends Letter
? ReadLettersHelper<Remainder, `${Acc}${L}`>
: [Acc, Input]
: [Acc, ''];
/**
* Reads a consecutive sequence of more than 1 double-quoted letters,
* where letters are `[^"]`.
*/
type ReadQuotedLetters<Input extends string> = string extends Input
? GenericStringError
: Input extends `"${infer Remainder}`
? ReadQuotedLettersHelper<Remainder, ''> extends [
`${infer Letters}`,
`${infer Remainder}`,
]
? Letters extends ''
? ParserError<`Expected string at \`${Remainder}\``>
: [Letters, Remainder]
: ReadQuotedLettersHelper<Remainder, ''>
: ParserError<`Not a double-quoted string at \`${Input}\``>;
type ReadQuotedLettersHelper<
Input extends string,
Acc extends string,
> = string extends Input
? GenericStringError
: Input extends `${infer L}${infer Remainder}`
? L extends '"'
? [Acc, Remainder]
: ReadQuotedLettersHelper<Remainder, `${Acc}${L}`>
: ParserError<`Missing closing double-quote in \`"${Acc}${Input}\``>;
/**
* Parses a (possibly double-quoted) identifier.
* For now, identifiers are just sequences of more than 1 letter.
*/
type ParseIdentifier<Input extends string> = ReadLetters<Input> extends [
infer Name,
`${infer Remainder}`,
]
? [Name, `${Remainder}`]
: ReadQuotedLetters<Input> extends [infer Name, `${infer Remainder}`]
? [Name, `${Remainder}`]
: ParserError<`No (possibly double-quoted) identifier at \`${Input}\``>;
/**
* Parses a node.
* A node is one of the following:
* - `*`
* - `field`
* - `field->json...`
* - `field(nodes)`
* - `field!hint(nodes)`
* - `field!inner(nodes)`
* - `field!hint!inner(nodes)`
* - `renamed_field:field`
* - `renamed_field:field->json...`
* - `renamed_field:field(nodes)`
* - `renamed_field:field!hint(nodes)`
* - `renamed_field:field!inner(nodes)`
* - `renamed_field:field!hint!inner(nodes)`
*
* TODO: casting operators `::text`, more support for JSON operators `->`, `->>`.
*/
type ParseNode<Input extends string> = Input extends ''
? ParserError<'Empty string'>
: // `*`
Input extends `*${infer Remainder}`
? [{ star: true }, EatWhitespace<Remainder>]
: ParseIdentifier<Input> extends [infer Name, `${infer Remainder}`]
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? // `field!inner(nodes)`
[
{ name: Name; original: Name; children: Fields },
EatWhitespace<Remainder>,
]
: ParseEmbeddedResource<
EatWhitespace<Remainder>
> extends ParserError<string>
? ParseEmbeddedResource<EatWhitespace<Remainder>>
: ParserError<'Expected embedded resource after `!inner`'>
: EatWhitespace<Remainder> extends `!${infer Remainder}`
? ParseIdentifier<EatWhitespace<Remainder>> extends [
infer _Hint,
`${infer Remainder}`,
]
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? // `field!hint!inner(nodes)`
[
{ name: Name; original: Name; children: Fields },
EatWhitespace<Remainder>,
]
: ParseEmbeddedResource<
EatWhitespace<Remainder>
> extends ParserError<string>
? ParseEmbeddedResource<EatWhitespace<Remainder>>
: ParserError<'Expected embedded resource after `!inner`'>
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? // `field!hint(nodes)`
[
{ name: Name; original: Name; children: Fields },
EatWhitespace<Remainder>,
]
: ParseEmbeddedResource<
EatWhitespace<Remainder>
> extends ParserError<string>
? ParseEmbeddedResource<EatWhitespace<Remainder>>
: ParserError<'Expected embedded resource after `!hint`'>
: ParserError<'Expected identifier after `!`'>
: EatWhitespace<Remainder> extends `:${infer Remainder}`
? ParseIdentifier<EatWhitespace<Remainder>> extends [
infer OriginalName,
`${infer Remainder}`,
]
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? // `renamed_field:field!inner(nodes)`
[
{ name: Name; original: OriginalName; children: Fields },
EatWhitespace<Remainder>,
]
: ParseEmbeddedResource<
EatWhitespace<Remainder>
> extends ParserError<string>
? ParseEmbeddedResource<EatWhitespace<Remainder>>
: ParserError<'Expected embedded resource after `!inner`'>
: EatWhitespace<Remainder> extends `!${infer Remainder}`
? ParseIdentifier<EatWhitespace<Remainder>> extends [
infer _Hint,
`${infer Remainder}`,
]
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? // `renamed_field:field!hint!inner(nodes)`
[
{ name: Name; original: OriginalName; children: Fields },
EatWhitespace<Remainder>,
]
: ParseEmbeddedResource<
EatWhitespace<Remainder>
> extends ParserError<string>
? ParseEmbeddedResource<EatWhitespace<Remainder>>
: ParserError<'Expected embedded resource after `!inner`'>
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? // `renamed_field:field!hint(nodes)`
[
{
name: Name;
original: OriginalName;
children: Fields;
},
EatWhitespace<Remainder>,
]
: ParseEmbeddedResource<
EatWhitespace<Remainder>
> extends ParserError<string>
? ParseEmbeddedResource<EatWhitespace<Remainder>>
: ParserError<'Expected embedded resource after `!hint`'>
: ParserError<'Expected identifier after `!`'>
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? // `renamed_field:field(nodes)`
[
{ name: Name; original: OriginalName; children: Fields },
EatWhitespace<Remainder>,
]
: ParseJsonAccessor<EatWhitespace<Remainder>> extends [
infer _PropertyName,
infer PropertyType,
`${infer Remainder}`,
]
? // `renamed_field:field->json...`
[{ name: Name; type: PropertyType }, EatWhitespace<Remainder>]
: ParseEmbeddedResource<
EatWhitespace<Remainder>
> extends ParserError<string>
? ParseEmbeddedResource<EatWhitespace<Remainder>>
: // `renamed_field:field`
[{ name: Name; original: OriginalName }, EatWhitespace<Remainder>]
: ParseIdentifier<EatWhitespace<Remainder>>
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? // `field(nodes)`
[
{ name: Name; original: Name; children: Fields },
EatWhitespace<Remainder>,
]
: ParseJsonAccessor<EatWhitespace<Remainder>> extends [
infer PropertyName,
infer PropertyType,
`${infer Remainder}`,
]
? // `field->json...`
[{ name: PropertyName; type: PropertyType }, EatWhitespace<Remainder>]
: ParseEmbeddedResource<
EatWhitespace<Remainder>
> extends ParserError<string>
? ParseEmbeddedResource<EatWhitespace<Remainder>>
: // `field`
[{ name: Name; original: Name }, EatWhitespace<Remainder>]
: ParserError<`Expected identifier at \`${Input}\``>;
/**
* Parses a JSON property accessor of the shape `->a->b->c`. The last accessor in
* the series may convert to text by using the ->> operator instead of ->.
*
* Returns a tuple of ["Last property name", "Last property type", "Remainder of text"]
* or the original string input indicating that no opening `->` was found.
*/
type ParseJsonAccessor<Input extends string> =
Input extends `->${infer Remainder}`
? Remainder extends `>${infer Remainder}`
? ParseIdentifier<Remainder> extends [infer Name, `${infer Remainder}`]
? [Name, string, EatWhitespace<Remainder>]
: ParserError<'Expected property name after `->>`'>
: ParseIdentifier<Remainder> extends [infer Name, `${infer Remainder}`]
? ParseJsonAccessor<Remainder> extends [
infer PropertyName,
infer PropertyType,
`${infer Remainder}`,
]
? [PropertyName, PropertyType, EatWhitespace<Remainder>]
: [Name, Json, EatWhitespace<Remainder>]
: ParserError<'Expected property name after `->`'>
: Input;
/**
* Parses an embedded resource, which is an opening `(`, followed by a sequence of
* nodes, separated by `,`, then a closing `)`.
*
* Returns a tuple of ["Parsed fields", "Remainder of text"], an error,
* or the original string input indicating that no opening `(` was found.
*/
type ParseEmbeddedResource<Input extends string> =
Input extends `(${infer Remainder}`
? ParseNodes<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`,
]
? EatWhitespace<Remainder> extends `)${infer Remainder}`
? Fields extends []
? ParserError<'Expected fields after `(`'>
: [Fields, EatWhitespace<Remainder>]
: ParserError<`Expected ")"`>
: ParseNodes<EatWhitespace<Remainder>>
: Input;
/**
* Parses a sequence of nodes, separated by `,`.
*
* Returns a tuple of ["Parsed fields", "Remainder of text"] or an error.
*/
type ParseNodes<Input extends string> = string extends Input
? GenericStringError
: ParseNodesHelper<Input, []>;
type ParseNodesHelper<
Input extends string,
Fields extends unknown[],
> = ParseNode<Input> extends [infer Field, `${infer Remainder}`]
? EatWhitespace<Remainder> extends `,${infer Remainder}`
? ParseNodesHelper<EatWhitespace<Remainder>, [Field, ...Fields]>
: [[Field, ...Fields], EatWhitespace<Remainder>]
: ParseNode<Input>;
/**
* Parses a query.
* A query is a sequence of nodes, separated by `,`, ensuring that there is
* no remaining input after all nodes have been parsed.
*
* Returns an array of parsed nodes, or an error.
*/
type ParseQuery<Query extends string> = string extends Query
? GenericStringError
: ParseNodes<EatWhitespace<Query>> extends [
infer Fields,
`${infer Remainder}`,
]
? EatWhitespace<Remainder> extends ''
? Fields
: ParserError<`Unexpected input: ${Remainder}`>
: ParseNodes<EatWhitespace<Query>>;
type GetResultHelper<
Schema extends GenericSchema,
Row extends Record<string, unknown>,
Fields extends unknown[],
Opts extends OptFlags,
Acc,
> = Fields extends [infer R]
? GetResultHelper<
Schema,
Row,
[],
Opts,
ConstructFieldDefinition<Schema, Row, R, Opts> & Acc
>
: Fields extends [infer R, ...infer Rest]
? GetResultHelper<
Schema,
Row,
Rest,
Opts,
ConstructFieldDefinition<Schema, Row, R, Opts> & Acc
>
: Prettify<Acc>;
/**
* Constructs a type definition for an object based on a given PostgREST query.
*
* @param Row Record<string, unknown>.
* @param Query Select query string literal to parse.
*/
export type GetResult<
Schema extends GenericSchema,
Row extends Record<string, unknown>,
Query extends string,
Opts extends OptFlags = { null: true; single: true; many: true },
> = ParseQuery<Query> extends unknown[]
? GetResultHelper<Schema, Row, ParseQuery<Query>, Opts, unknown>
: ParseQuery<Query>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment