Skip to content

Commit

Permalink
feature(conditionals): made key a configuration option
Browse files Browse the repository at this point in the history
  • Loading branch information
scottrippey committed Jan 16, 2024
1 parent 53852f6 commit 90d612f
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 36 deletions.
55 changes: 36 additions & 19 deletions packages/groq-builder/src/commands/conditional$.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { describe, expect, it } from "vitest";
import { createGroqBuilder, GroqBuilder, InferResultType } from "../index";
import {
createGroqBuilder,
GroqBuilder,
InferResultItem,
InferResultType,
} from "../index";
import { SchemaConfig } from "../tests/schemas/nextjs-sanity-fe";
import { ExtractConditionalProjectionTypes } from "./conditional-types";
import { expectType } from "../tests/expectType";
Expand Down Expand Up @@ -134,29 +139,41 @@ describe("conditional$", () => {
}),
...qV.conditional$(
{
"another == condition": { foo: q.value("FOO") },
"another == condition1": { foo: q.value("FOO") },
"another == condition2": { bar: q.value("BAR") },
},
"unique-key"
{ key: "unique-key" }
),
}));

it("the types should be inferred correctly", () => {
expectType<InferResultType<typeof qMultipleConditions>>().toStrictEqual<
Array<
| { name: string }
| { name: string; onSale: false }
| { name: string; onSale: true; price: number; msrp: number }
| { name: string; foo: "FOO" }
| { name: string; onSale: false; foo: "FOO" }
| {
name: string;
onSale: true;
price: number;
msrp: number;
foo: "FOO";
}
>
>();
type ActualItem = InferResultItem<typeof qMultipleConditions>;
type ExpectedItem =
| { name: string }
| { name: string; onSale: false }
| { name: string; onSale: true; price: number; msrp: number }
| { name: string; foo: "FOO" }
| { name: string; onSale: false; foo: "FOO" }
| {
name: string;
onSale: true;
price: number;
msrp: number;
foo: "FOO";
}
| { name: string; bar: "BAR" }
| { name: string; onSale: false; bar: "BAR" }
| {
name: string;
onSale: true;
price: number;
msrp: number;
bar: "BAR";
};

type Remainder = Exclude<ActualItem, ExpectedItem>;
expectType<Remainder>().toStrictEqual<never>();
expectType<ActualItem>().toStrictEqual<ExpectedItem>();
});

it("the query should be compiled correctly", () => {
Expand Down
10 changes: 7 additions & 3 deletions packages/groq-builder/src/commands/conditional$.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GroqBuilder } from "../groq-builder";
import { ResultItem } from "../types/result-types";
import {
ConditionalConfig,
ConditionalKey,
ConditionalProjectionMap,
ExtractConditionalProjectionResults,
Expand All @@ -21,7 +22,7 @@ declare module "../groq-builder" {
TKey extends string = "[$]"
>(
conditionalProjections: TConditionalProjections,
conditionalKey?: TKey
config?: ConditionalConfig<TKey>
): ExtractConditionalProjectionResults<
ResultItem<TResult>,
TConditionalProjections,
Expand All @@ -34,7 +35,7 @@ GroqBuilder.implement({
conditional$<TCP extends object, TKey extends string>(
this: GroqBuilder,
conditionalProjections: TCP,
conditionalKey = "[$]" as TKey
config?: ConditionalConfig<TKey>
) {
const root = this.root;
const allConditionalProjections = Object.entries(
Expand All @@ -60,13 +61,16 @@ GroqBuilder.implement({
: createConditionalParserUnion(parsers);

const conditionalQuery = root.chain(query, conditionalParser);
const uniqueKey: ConditionalKey<TKey> = `[Conditional] ${conditionalKey}`;
const uniqueKey: ConditionalKey<TKey> = `[Conditional] ${
config?.key ?? ("[$]" as TKey)
}`;

return {
[uniqueKey]: conditionalQuery,
} as unknown as SpreadableConditionals<TKey, any>;
},
});

function createConditionalParserUnion(parsers: ParserFunction[]) {
return function parserUnion(input: unknown) {
for (const parser of parsers) {
Expand Down
23 changes: 13 additions & 10 deletions packages/groq-builder/src/commands/conditional-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,17 @@ export type ExtractConditionalByTypeProjectionResults<
TKey extends string
> = SpreadableConditionals<
TKey,
ValueOf<{
[_type in keyof TConditionalByTypeProjectionMap]: ExtractProjectionResult<
Extract<TResultItem, { _type: _type }>,
TConditionalByTypeProjectionMap[_type] extends (
q: any
) => infer TProjectionMap
? TProjectionMap
: TConditionalByTypeProjectionMap[_type]
>;
}>
| Empty
| ValueOf<{
[_type in keyof TConditionalByTypeProjectionMap]: ExtractProjectionResult<
Extract<TResultItem, { _type: _type }>,
TConditionalByTypeProjectionMap[_type] extends (
q: any
) => infer TProjectionMap
? TProjectionMap
: TConditionalByTypeProjectionMap[_type]
>;
}>
>;

export type ConditionalKey<TKey extends string> = `[Conditional] ${TKey}`;
Expand All @@ -97,3 +98,5 @@ export type SpreadableConditionals<
> = {
[UniqueConditionalKey in ConditionalKey<TKey>]: IGroqBuilder<ConditionalResultType>;
};

export type ConditionalConfig<TKey> = { key: TKey };
57 changes: 56 additions & 1 deletion packages/groq-builder/src/commands/conditionalByType.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import {
ExtractTypeNames,
GroqBuilder,
IGroqBuilder,
InferResultItem,
InferResultType,
} from "../index";
import { SchemaConfig } from "../tests/schemas/nextjs-sanity-fe";
import { ExtractConditionalProjectionTypes } from "./conditional-types";
import { expectType } from "../tests/expectType";
import { executeBuilder } from "../tests/mocks/executeQuery";
import { mock } from "../tests/mocks/nextjs-sanity-fe-mocks";
import { Empty, SimplifyDeep } from "../types/utils";
import { Empty, Simplify, SimplifyDeep } from "../types/utils";

const q = createGroqBuilder<SchemaConfig>({ indent: " " });
const data = mock.generateSeedData({
Expand All @@ -32,6 +33,7 @@ describe("conditionalByType", () => {
});

type ExpectedConditionalUnion =
| Empty
| { _type: "variant"; name: string; price: number }
| { _type: "product"; name: string; slug: string }
| { _type: "category"; name: string; slug: string };
Expand All @@ -48,6 +50,59 @@ describe("conditionalByType", () => {
});
});

describe("multiple conditionals can be spread", () => {
const qMultiple = q.star.project((q) => ({
...q.conditionalByType({
variant: { price: true },
product: { slug: "slug.current" },
}),
...q.conditionalByType(
{
category: { description: true },
style: { name: true },
},
{ key: "unique-key" }
),
}));

it("should infer the correct type", () => {
type ActualItem = Simplify<InferResultItem<typeof qMultiple>>;
type ExpectedItem =
| Empty
| { price: number }
| { slug: string }
| { description: string | undefined }
| { name: string | undefined }
| { price: number; description: string | undefined }
| { price: number; name: string | undefined }
| { slug: string; description: string | undefined }
| { slug: string; name: string | undefined };

type Remainder = Exclude<ActualItem, ExpectedItem>;
expectType<Remainder>().toStrictEqual<never>();
expectType<ActualItem>().toStrictEqual<ExpectedItem>();
});

it("the query should be correct", () => {
expect(qMultiple.query).toMatchInlineSnapshot(`
"* {
_type == \\"variant\\" => {
price
},
_type == \\"product\\" => {
\\"slug\\": slug.current
},
_type == \\"category\\" => {
description
},
_type == \\"style\\" => {
name
}
}"
`);
});
});

it("should be able to extract the return types", () => {
type ConditionalResults = ExtractConditionalProjectionTypes<
typeof conditionalByType
Expand Down
9 changes: 6 additions & 3 deletions packages/groq-builder/src/commands/conditionalByType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ConditionalByTypeProjectionMap,
ConditionalKey,
SpreadableConditionals,
ConditionalConfig,
} from "./conditional-types";
import { ProjectionMap } from "./projection-types";

Expand All @@ -19,7 +20,7 @@ declare module "../groq-builder" {
TKey extends string = "[ByType]"
>(
conditionalProjections: TConditionalProjections,
conditionalKey?: TKey
config?: ConditionalConfig<TKey>
): ExtractConditionalByTypeProjectionResults<
ResultItem<TResult>,
TConditionalProjections,
Expand All @@ -35,7 +36,7 @@ GroqBuilder.implement({
>(
this: GroqBuilder<any, RootConfig>,
conditionalProjections: TConditionalProjections,
conditionalKey = "[ByType]" as TKey
config?: ConditionalConfig<TKey>
) {
const typeNames = Object.keys(conditionalProjections);

Expand Down Expand Up @@ -66,7 +67,9 @@ GroqBuilder.implement({
};

const conditionalQuery = this.root.chain(query, conditionalParser);
const uniqueKey: ConditionalKey<string> = `[Conditional] ${conditionalKey}`;
const uniqueKey: ConditionalKey<string> = `[Conditional] ${
config?.key ?? ("[ByType]" as TKey)
}`;
return {
[uniqueKey]: conditionalQuery,
} as unknown as SpreadableConditionals<TKey, any>;
Expand Down

0 comments on commit 90d612f

Please sign in to comment.