Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

groq-builder: strongly-typed parameters (variables) and conditionals #263

Merged
merged 22 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
65e80b9
feature(variables): renamed `TRootConfig` to `TQueryConfig`
Feb 3, 2024
7e03d4c
feature(variables): added variables command
Feb 4, 2024
36595ad
feature(variables): improved `makeSafeQueryRunner` to handle variable…
Feb 4, 2024
7f49ca6
feature(variables): added changeset
Feb 5, 2024
53735d3
feature(variables): added conditional statement auto-complete suggest…
Feb 5, 2024
cb9ef3f
feature(variables): improved auto-complete suggestions
Feb 5, 2024
f9533a2
feature(variables): added strongly-typed `filterBy`
Feb 5, 2024
a1a9b4d
feature(variables): fixed minor issue with InferResultItem / InferVar…
Feb 5, 2024
1d1ecfa
feature(variables): use uppercase keys for conditionals
Feb 5, 2024
e0fd46c
feature(variables): updated conditionals to accept IGroqBuilder
Feb 5, 2024
4724a64
feature(variables): allow paths within conditionals
Feb 5, 2024
462fca9
feature(variables): extracted `createGroqBuilder` into separate file
Feb 5, 2024
ea4bf45
feature(variables): renamed 'variables' to 'parameters' for better co…
Feb 5, 2024
c2c13b2
feature(variables): renamed 'variables' to 'parameters' for better co…
Feb 5, 2024
a6e0735
feature(variables): infer raw type from parser
Feb 6, 2024
bed9072
feature(variables): moved createGroqBuilder back into index, since th…
Feb 6, 2024
df23dde
feature(variables): added jsdocs
Feb 6, 2024
2072a0d
feature(variables): updated lockfile
Feb 6, 2024
e615ccd
feature(variables): support deeply-nested parameters
Feb 6, 2024
4e1b55c
feature(variables): improved docs for makeSafeQueryRunner
Feb 6, 2024
39756f4
feature(variables): improved code for expression suggestions
Feb 6, 2024
4ba3746
feature(variables): improved changeset
Feb 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/cyan-hats-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"groq-builder": minor
---

Added support for parameters.

Added `filterBy` for strongly-typed filtering.

Added auto-complete help for conditional statements and filters.

Changed how parameters are passed to `makeSafeQueryRunner`.

3 changes: 2 additions & 1 deletion packages/groq-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
],
"main": "./dist/index.js",
"sideEffects": [
"./dist/commands/**"
"./dist/commands/**",
"./dist/groq-builder"
],
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
Expand Down
32 changes: 14 additions & 18 deletions packages/groq-builder/src/commands/conditional-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,23 @@ import {
ProjectionMapOrCallback,
} from "./projection-types";
import { Empty, IntersectionOfValues, Simplify, ValueOf } from "../types/utils";
import { ExtractTypeNames, RootConfig } from "../types/schema-types";
import { ExtractTypeNames, QueryConfig } from "../types/schema-types";
import { GroqBuilder } from "../groq-builder";
import { IGroqBuilder, InferResultType } from "../types/public-types";
import { Expressions } from "../types/groq-expressions";

export type ConditionalProjectionMap<
TResultItem,
TRootConfig extends RootConfig
> = {
[Condition: ConditionalExpression<TResultItem>]:
TQueryConfig extends QueryConfig
> = Partial<
Record<
Expressions.AnyConditional<TResultItem, TQueryConfig>,
| ProjectionMap<TResultItem>
| ((
q: GroqBuilder<TResultItem, TRootConfig>
) => ProjectionMap<TResultItem>);
};

/**
* For now, none of our "conditions" are strongly-typed,
* so we'll just use "string":
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type ConditionalExpression<TResultItem> = string;
q: GroqBuilder<TResultItem, TQueryConfig>
) => ProjectionMap<TResultItem>)
>
>;

export type ExtractConditionalProjectionResults<
TResultItem,
Expand Down Expand Up @@ -52,11 +48,11 @@ export type ExtractConditionalProjectionTypes<TProjectionMap> = Simplify<

export type ConditionalByTypeProjectionMap<
TResultItem,
TRootConfig extends RootConfig
TQueryConfig extends QueryConfig
> = {
[_type in ExtractTypeNames<TResultItem>]?: ProjectionMapOrCallback<
Extract<TResultItem, { _type: _type }>,
TRootConfig
TQueryConfig
>;
};

Expand All @@ -82,9 +78,9 @@ export type ExtractConditionalByTypeProjectionResults<
}>
>;

export type ConditionalKey<TKey extends string> = `[Conditional] ${TKey}`;
export type ConditionalKey<TKey extends string> = `[CONDITIONAL] ${TKey}`;
export function isConditional(key: string): key is ConditionalKey<string> {
return key.startsWith("[Conditional] ");
return key.startsWith("[CONDITIONAL] ");
}
export type SpreadableConditionals<
TKey extends string,
Expand Down
10 changes: 5 additions & 5 deletions packages/groq-builder/src/commands/conditional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ExtractConditionalProjectionTypes } from "./conditional-types";
import { Empty, Simplify } from "../types/utils";

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

describe("conditional", () => {
describe("by itself", () => {
Expand All @@ -36,14 +36,14 @@ describe("conditional", () => {
});
it("should return a spreadable object", () => {
expect(conditionalResult).toMatchObject({
"[Conditional] [$]": expect.any(GroqBuilder),
"[CONDITIONAL] [KEY]": expect.any(GroqBuilder),
});
});
});

const qAll = qBase.project((qA) => ({
const qAll = qVariants.project((qV) => ({
name: true,
...qA.conditional({
...qV.conditional({
"price == msrp": {
onSale: q.value(false),
},
Expand Down Expand Up @@ -142,7 +142,7 @@ describe("conditional", () => {
"another == condition1": { foo: q.value("FOO") },
"another == condition2": { bar: q.value("BAR") },
},
{ key: "unique-key" }
{ key: "[UNIQUE-KEY]" }
),
}));

Expand Down
14 changes: 8 additions & 6 deletions packages/groq-builder/src/commands/conditional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import { ProjectionMap } from "./projection-types";

declare module "../groq-builder" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export interface GroqBuilder<TResult, TRootConfig> {
export interface GroqBuilder<TResult, TQueryConfig> {
conditional<
TConditionalProjections extends ConditionalProjectionMap<
ResultItem.Infer<TResult>,
TRootConfig
TQueryConfig
>,
TKey extends string = "[$]",
TKey extends string = typeof DEFAULT_KEY,
TIsExhaustive extends boolean = false
>(
conditionalProjections: TConditionalProjections,
Expand All @@ -31,6 +31,8 @@ declare module "../groq-builder" {
}
}

const DEFAULT_KEY = "[KEY]" as const;

GroqBuilder.implement({
conditional<
TCP extends object,
Expand Down Expand Up @@ -58,15 +60,15 @@ GroqBuilder.implement({
.join(`,${newLine}`);

const parsers = allConditionalProjections
.map((q) => q.internal.parser)
.map((q) => q.parser)
.filter(notNull);
const conditionalParser = !parsers.length
? null
: createConditionalParserUnion(parsers, config?.isExhaustive ?? false);

const conditionalQuery = root.chain(query, conditionalParser);
const key = config?.key || ("[$]" as TKey);
const conditionalKey: ConditionalKey<TKey> = `[Conditional] ${key}`;
const key = config?.key || (DEFAULT_KEY as TKey);
const conditionalKey: ConditionalKey<TKey> = `[CONDITIONAL] ${key}`;
return {
[conditionalKey]: conditionalQuery,
} as any;
Expand Down
6 changes: 3 additions & 3 deletions packages/groq-builder/src/commands/conditionalByType.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ describe("conditionalByType", () => {
it('should have a "spreadable" signature', () => {
expectTypeOf<SimplifyDeep<typeof conditionalByType>>().toEqualTypeOf<
SimplifyDeep<{
"[Conditional] [ByType]": IGroqBuilder<ExpectedConditionalUnion>;
"[CONDITIONAL] [BY_TYPE]": IGroqBuilder<ExpectedConditionalUnion>;
}>
>();

expect(conditionalByType).toMatchObject({
"[Conditional] [ByType]": expect.any(GroqBuilder),
"[CONDITIONAL] [BY_TYPE]": expect.any(GroqBuilder),
});
});

Expand Down Expand Up @@ -183,7 +183,7 @@ describe("conditionalByType", () => {
});

it("should execute correctly", async () => {
const res = await executeBuilder(qAll, data.datalake);
const res = await executeBuilder(qAll, data);

expect(res.find((item) => item._type === "category"))
.toMatchInlineSnapshot(`
Expand Down
15 changes: 8 additions & 7 deletions packages/groq-builder/src/commands/conditionalByType.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GroqBuilder } from "../groq-builder";
import { ExtractTypeNames, RootConfig } from "../types/schema-types";
import { ExtractTypeNames, QueryConfig } from "../types/schema-types";
import { ResultItem } from "../types/result-types";
import {
ExtractConditionalByTypeProjectionResults,
Expand All @@ -10,13 +10,13 @@ import {
import { ProjectionMap } from "./projection-types";

declare module "../groq-builder" {
export interface GroqBuilder<TResult, TRootConfig> {
export interface GroqBuilder<TResult, TQueryConfig> {
conditionalByType<
TConditionalProjections extends ConditionalByTypeProjectionMap<
ResultItem.Infer<TResult>,
TRootConfig
TQueryConfig
>,
TKey extends string = "[ByType]",
TKey extends string = typeof DEFAULT_KEY,
/**
* Did we supply a condition for all possible _type values?
*/
Expand All @@ -35,14 +35,15 @@ declare module "../groq-builder" {
>;
}
}
const DEFAULT_KEY = "[BY_TYPE]" as const;

GroqBuilder.implement({
conditionalByType<
TConditionalProjections extends object,
TKey extends string,
TIsExhaustive extends boolean
>(
this: GroqBuilder<any, RootConfig>,
this: GroqBuilder<any, QueryConfig>,
conditionalProjections: TConditionalProjections,
config?: Partial<ConditionalConfig<TKey, TIsExhaustive>>
) {
Expand Down Expand Up @@ -80,8 +81,8 @@ GroqBuilder.implement({
};

const conditionalQuery = this.root.chain(query, conditionalParser);
const key: TKey = config?.key || ("[ByType]" as TKey);
const conditionalKey: ConditionalKey<TKey> = `[Conditional] ${key}`;
const key: TKey = config?.key || (DEFAULT_KEY as TKey);
const conditionalKey: ConditionalKey<TKey> = `[CONDITIONAL] ${key}`;
return {
_type: true, // Ensure we request the `_type` parameter
[conditionalKey]: conditionalQuery,
Expand Down
7 changes: 4 additions & 3 deletions packages/groq-builder/src/commands/deref.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import { InferResultType } from "../types/public-types";
import { createGroqBuilder } from "../index";
import { executeBuilder } from "../tests/mocks/executeQuery";
import { mock } from "../tests/mocks/nextjs-sanity-fe-mocks";
import { SanitySchema, SchemaConfig } from "../tests/schemas/nextjs-sanity-fe";

import { createGroqBuilder } from "../index";

const q = createGroqBuilder<SchemaConfig>();
const data = mock.generateSeedData({});

Expand Down Expand Up @@ -48,11 +49,11 @@ describe("deref", () => {
});

it("should execute correctly (single)", async () => {
const results = await executeBuilder(qCategory, data.datalake);
const results = await executeBuilder(qCategory, data);
expect(results).toEqual(data.categories[0]);
});
it("should execute correctly (multiple)", async () => {
const results = await executeBuilder(qVariants, data.datalake);
const results = await executeBuilder(qVariants, data);
expect(results).toEqual(data.variants);
});
});
10 changes: 5 additions & 5 deletions packages/groq-builder/src/commands/deref.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { GroqBuilder } from "../groq-builder";
import { ExtractRefType, RootConfig } from "../types/schema-types";
import { ExtractRefType, QueryConfig } from "../types/schema-types";
import { ResultItem } from "../types/result-types";

declare module "../groq-builder" {
export interface GroqBuilder<TResult, TRootConfig> {
export interface GroqBuilder<TResult, TQueryConfig> {
deref<
TReferencedType = ExtractRefType<ResultItem.Infer<TResult>, TRootConfig>
TReferencedType = ExtractRefType<ResultItem.Infer<TResult>, TQueryConfig>
>(): GroqBuilder<
ResultItem.Override<TResult, TReferencedType>,
TRootConfig
TQueryConfig
>;
}
}

GroqBuilder.implement({
deref(this: GroqBuilder<any, RootConfig>) {
deref(this: GroqBuilder<any, QueryConfig>) {
return this.chain("->");
},
});
Loading
Loading