Skip to content

Commit

Permalink
feature(conditionals): changed signature for conditional projections;…
Browse files Browse the repository at this point in the history
… uses 2nd parameter
  • Loading branch information
scottrippey committed Dec 28, 2023
1 parent c936ab4 commit f32fdf1
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 30 deletions.
51 changes: 48 additions & 3 deletions packages/groq-builder/src/commands/conditional$.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { describe, it } from "vitest";
import { createGroqBuilder } from "../index";
import { describe, expect, it } from "vitest";
import { createGroqBuilder, InferResultType } from "../index";
import { SchemaConfig } from "../tests/schemas/nextjs-sanity-fe";
import { ExtractConditionalProjectionTypes } from "./conditional-types";
import { expectType } from "../tests/expectType";

const q = createGroqBuilder<SchemaConfig>();
const q = createGroqBuilder<SchemaConfig>({ indent: " " });
const qBase = q.star.filterByType("variant");

describe("conditional$", () => {
it("by itself, we should be able to extract the union of projection types", () => {
Expand All @@ -25,4 +26,48 @@ describe("conditional$", () => {
{ name: string; price: number } | { name: string; msrp: number }
>();
});

const qAll = qBase.project(
{
name: true,
},
(qA) =>
qA.conditional$({
"price == msrp": {
onSale: q.value(false),
},
"price < msrp": {
onSale: q.value(true),
price: true,
msrp: true,
},
})
);

it("should be able to extract the return type", () => {
expectType<InferResultType<typeof qAll>>().toStrictEqual<
Array<
| { name: string; onSale: false }
| { name: string; onSale: true; price: number; msrp: number }
>
>();
});

it("the query should look correct", () => {
expect(qAll.query).toMatchInlineSnapshot(
`
"*[_type == \\"variant\\"] {
name,
price == msrp => {
\\"onSale\\": false
},
price < msrp => {
\\"onSale\\": true,
price,
msrp
}
}"
`
);
});
});
5 changes: 3 additions & 2 deletions packages/groq-builder/src/commands/conditional$.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ declare module "../groq-builder" {
GroqBuilder.implement({
conditional$(this: GroqBuilder, conditionalProjections): any {
// Return an object; the `project` method will turn it into a query.
const root = this.root;
return Object.fromEntries(
Object.entries(conditionalProjections).map(
([condition, projectionMap]) => {
if (typeof projectionMap === "function") {
projectionMap = projectionMap(this.root);
projectionMap = projectionMap(root);
}

const projection = this.root
const projection = root
.chain(`${condition} => `)
.project(projectionMap);

Expand Down
17 changes: 10 additions & 7 deletions packages/groq-builder/src/commands/conditionalByType.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ describe("conditionalByType", () => {
}),
});

const qAll = q.star.project((qA) => ({
_type: true,
...qA.conditionalByType({
product: { name: true, slug: "slug.current" },
variant: { name: true, price: true },
}),
}));
const qAll = q.star.project(
{
_type: true,
},
(qA) =>
qA.conditionalByType({
product: { name: true, slug: "slug.current" },
variant: { name: true, price: true },
})
);

it("we should be able to extract the return types", () => {
type ConditionalResults = ExtractConditionalProjectionTypes<
Expand Down
36 changes: 30 additions & 6 deletions packages/groq-builder/src/commands/project.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
import { notNull, Simplify } from "../types/utils";
import { Empty, notNull, Simplify } from "../types/utils";
import { GroqBuilder } from "../groq-builder";
import { Parser, ParserFunction } from "../types/public-types";
import { isParser, normalizeValidationFunction } from "./validate-utils";
import { ResultItem, ResultOverride } from "../types/result-types";
import { ValidationErrors } from "../validation/validation-errors";
import { ExtractProjectionResult, ProjectionMap } from "./projection-types";
import {
ConditionalProjectionResultWrapper,
ExtractConditionalProjectionTypes,
} from "./conditional-types";

declare module "../groq-builder" {
export interface GroqBuilder<TResult, TRootConfig> {
/**
* Performs an "object projection", returning an object with the fields specified.
* @param projectionMap
*/
project<TProjection extends ProjectionMap<ResultItem<TResult>>>(
project<
TProjection extends ProjectionMap<ResultItem<TResult>>,
TConditionals extends
| ConditionalProjectionResultWrapper<any>
| undefined = undefined
>(
projectionMap:
| TProjection
| ((q: GroqBuilder<ResultItem<TResult>, TRootConfig>) => TProjection)
| ((q: GroqBuilder<ResultItem<TResult>, TRootConfig>) => TProjection),
conditionalProjections?:
| TConditionals
| ((q: GroqBuilder<ResultItem<TResult>, TRootConfig>) => TConditionals)
): GroqBuilder<
ResultOverride<
TResult,
Simplify<ExtractProjectionResult<ResultItem<TResult>, TProjection>>
Simplify<
ExtractProjectionResult<ResultItem<TResult>, TProjection> &
(TConditionals extends undefined
? Empty
: ExtractConditionalProjectionTypes<TConditionals>)
>
>,
TRootConfig
>;
Expand All @@ -29,7 +45,8 @@ declare module "../groq-builder" {
GroqBuilder.implement({
project(
this: GroqBuilder,
projectionMapArg: object | ((q: GroqBuilder) => object)
projectionMapArg: object | ((q: any) => object),
conditionalProjections?: object | ((q: any) => object)
): GroqBuilder<any> {
// Retrieve the projectionMap:
let projectionMap: object;
Expand All @@ -38,6 +55,13 @@ GroqBuilder.implement({
} else {
projectionMap = projectionMapArg;
}
if (conditionalProjections) {
if (typeof conditionalProjections === "function") {
conditionalProjections = conditionalProjections(this.root);
}
// Just push the conditions into the `projectionMap` since the logic is the same
Object.assign(projectionMap, conditionalProjections);
}

// Analyze all the projection values:
const keys = Object.keys(projectionMap) as Array<string>;
Expand Down
13 changes: 1 addition & 12 deletions packages/groq-builder/src/commands/projection-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ import { FragmentInputTypeTag, Parser } from "../types/public-types";
import { Path, PathEntries, PathValue } from "../types/path-types";
import { DeepRequired } from "../types/deep-required";
import { RootConfig } from "../types/schema-types";
import {
ConditionalProjectionResultTypesTag,
ConditionalProjectionResultWrapper,
} from "./conditional-types";

export type ProjectionKey<TResultItem> = IsAny<TResultItem> extends true
? string
Expand Down Expand Up @@ -63,19 +59,12 @@ type ProjectionFieldConfig<TResultItem, TFieldType> =

export type ExtractProjectionResult<TResultItem, TProjectionMap> =
(TProjectionMap extends { "...": true } ? TResultItem : Empty) &
(TProjectionMap extends ConditionalProjectionResultWrapper<
infer TConditionalTypes
>
? TConditionalTypes
: Empty) &
ExtractProjectionResultImpl<
TResultItem,
Omit<
TProjectionMap,
// Ensure we remove any "tags" that we don't want in the mapped type:
| "..."
| typeof ConditionalProjectionResultTypesTag
| typeof FragmentInputTypeTag
"..." | typeof FragmentInputTypeTag
>
>;

Expand Down

0 comments on commit f32fdf1

Please sign in to comment.