Skip to content

Instantly share code, notes, and snippets.

@mcky
Created November 16, 2020 14:33
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 mcky/4321f9d2c99667c715c7fd8b0d458e6f to your computer and use it in GitHub Desktop.
Save mcky/4321f9d2c99667c715c7fd8b0d458e6f to your computer and use it in GitHub Desktop.
typed groq queries
// Copied from https://twitter.com/MikeRyanDev/status/1308472279010025477
// Don't need most of the token parsing, but leaving it in until the POC is complete because I'll probably
// break it all trying to remove it
type Split<S extends string, D extends string> = S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];
type TakeLast<V> = V extends [] ? never : V extends [string] ? V[0] : V extends [string, ...infer R] ? TakeLast<R> : never;
type TrimLeft<V extends string> = V extends ` ${infer R}` ? TrimLeft<R> : V;
type TrimRight<V extends string> = V extends `${infer R} ` ? TrimRight<R> : V;
type Trim<V extends string> = TrimLeft<TrimRight<V>>;
type StripModifier<V extends string, M extends string> = V extends `${infer L}${M}${infer A}` ? L : V;
type StripModifiers<V extends string> = StripModifier<StripModifier<StripModifier<StripModifier<V, '.'>, '#'>, '['>, ':'>;
type TakeLastAfterToken<V extends string, T extends string> = StripModifiers<TakeLast<Split<Trim<V>, T>>>;
type GetLastElementName<V extends string> = TakeLastAfterToken<TakeLastAfterToken<V, ' '>, '>'>;
type GetEachElementName<V, L extends string[] = []> =
V extends []
? L
: V extends [string]
? [...L, GetLastElementName<V[0]>]
: V extends [string, ...infer R]
? GetEachElementName<R, [...L, GetLastElementName<V[0]>]>
: [];
// Split and trim
type GetElementNames<V extends string> = GetEachElementName<Split<V, ','>>;
type _debug_keyList = GetElementNames<"_id, title">
type ParseQueryBody<T extends string, S> = GetElementNames<T>[number] extends keyof S
? Pick<S, GetElementNames<T>[number]>
: never
type _debug_matchesSingular = ParseQueryBody<'_id', CommonIssue>
type _debug_matchesMultiple = ParseQueryBody<'_id, title', CommonIssue>
type _debug_matchesMultiple2 = ParseQueryBody<'_id, name', CommonIssue>
type ExtractByType<T, S extends string> = Extract<T, { _type: S}>
type Parse<T extends string, S> =
T extends `[_type == "${infer Type}"${infer RestQ}] {${infer Rest}}`
? ParseQueryBody<Rest, ExtractByType<S, Type>>
: never
// Test types
interface SanityDocument {
_id: string;
_createdAt: string;
_rev: string;
_updatedAt: string;
}
export interface PlantSpecies extends SanityDocument {
_type: "plantSpecies",
name: string;
}
export interface CommonIssue extends SanityDocument {
_type: "commonIssue"
title: string;
description: number;
}
type Documents = PlantSpecies | CommonIssue
type _debug_plants = Parse<'[_type == "plantSpecies"] { _id, name }', Documents>
type _debug_issues = Parse<'[_type == "commonIssue" && _id == 5] { _id, title }', Documents>
type _debug_issues_invalid_query = Parse<'[_type == "commonIssue" && _id == 5] { _id, title, nonexistant }', Documents>
declare function query<S extends string>(q: S): Parse<S, Documents>;
const invalidPlants = query('[_type == "plantSpecies"] { _id, name, foo }')
invalidPlants.name
const validPlants = query('[_type == "plantSpecies"] { _id, name }')
validPlants.name
const issuesWithoutDescriptions = query('[_type == "commonIssue" && _id == 5] { _id, title }')
issuesWithoutDescriptions.title
issuesWithoutDescriptions.description
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment