Skip to content

Commit c89516a

Browse files
authored
Fix: ensure we can get fields from blocks and references (#373)
* feature(blocks): ensure we can grab fields from blocks and references, when needed * changeset
1 parent 3fbb4f1 commit c89516a

4 files changed

Lines changed: 86 additions & 13 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"groqd": patch
3+
---
4+
5+
Fix: ensure we can get fields from `block` and `reference` objects

packages/groqd/src/tests/mocks/nextjs-sanity-fe-mocks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ export class MockFactory {
152152
children: [{ _type: "span", _key: "", text: "", marks: [] }],
153153
markDefs: [],
154154
style: undefined,
155+
listItem: undefined,
156+
level: undefined,
155157
...data,
156158
};
157159
}

packages/groqd/src/types/projection-paths.test.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { describe, it, expectTypeOf } from "vitest";
1+
import { describe, expectTypeOf, it } from "vitest";
2+
import { SanitySchema } from "../tests/schemas/nextjs-sanity-fe";
3+
import { Slug } from "../tests/schemas/nextjs-sanity-fe.sanity-typegen";
24
import {
35
ProjectionPathEntries,
46
ProjectionPaths,
57
ProjectionPathsByType,
68
ProjectionPathValue,
79
} from "./projection-paths";
8-
import { SanitySchema } from "../tests/schemas/nextjs-sanity-fe";
9-
import { Slug } from "../tests/schemas/nextjs-sanity-fe.sanity-typegen";
1010
import { UndefinedToNull } from "./utils";
1111

1212
type TestObject = {
@@ -252,12 +252,65 @@ describe("ProjectionPathEntries", () => {
252252
>();
253253
});
254254

255+
describe("built-in types should not be deeply parsed", () => {
256+
type Block = SanitySchema.Description[number];
257+
type Reference = NonNullable<SanitySchema.Variant["flavour"]>[number];
258+
type Example = {
259+
blocks: Block[];
260+
ref: Reference;
261+
optionalRef?: Reference;
262+
deep: { foo: "bar" };
263+
};
264+
it("should not show deep results for built-in types", () => {
265+
expectTypeOf<ProjectionPathEntries<Example>>().toEqualTypeOf<{
266+
"blocks[]": Block[];
267+
ref: Reference;
268+
optionalRef: Reference | null;
269+
deep: Example["deep"];
270+
"deep.foo": "bar";
271+
}>();
272+
});
273+
describe("when at the top level", () => {
274+
it("should show deep results for a 'block'", () => {
275+
type Actual = ProjectionPathEntries<Block>;
276+
type Expected = {
277+
_type: "block";
278+
_key: string;
279+
style: UndefinedToNull<Block["style"]>;
280+
level: UndefinedToNull<Block["level"]>;
281+
listItem: UndefinedToNull<Block["listItem"]>;
282+
"children[]": UndefinedToNull<Block["children"]>;
283+
"children[]._type": null | Array<"span">;
284+
"children[]._key": null | Array<string>;
285+
"children[].text": null | Array<string | null>;
286+
"children[].marks[]": null | Array<string[] | null>;
287+
"markDefs[]": UndefinedToNull<Block["markDefs"]>;
288+
"markDefs[]._type": null | Array<"link">;
289+
"markDefs[]._key": null | Array<string>;
290+
"markDefs[].href": null | Array<string | null>;
291+
};
292+
293+
expectTypeOf<Actual>().toEqualTypeOf<Expected>();
294+
});
295+
it("should show deep results for a 'reference'", () => {
296+
type Actual = ProjectionPathEntries<Reference>;
297+
type Expected = {
298+
_type: "reference";
299+
_key: string;
300+
_ref: string;
301+
_weak: null | boolean;
302+
};
303+
expectTypeOf<Actual>().toEqualTypeOf<Expected>();
304+
});
305+
});
306+
});
307+
255308
describe("when dealing with unions", () => {
256309
type Union = { _type: "TypeA"; a: "A" } | { _type: "TypeB"; b: "B" };
257310

258311
it("should only return types for common properties", () => {
259-
type Result = ProjectionPathEntries<Union>;
260-
expectTypeOf<Result>().toEqualTypeOf<{
312+
type Actual = ProjectionPathEntries<Union>;
313+
expectTypeOf<Actual>().toEqualTypeOf<{
261314
_type: "TypeA" | "TypeB";
262315
}>();
263316
});

packages/groqd/src/types/projection-paths.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,35 @@ import { CompatibleKeys, CompatiblePick } from "./compatible-types";
1313

1414
/**
1515
* These types are ignored when calculating projection paths,
16-
* since they're rarely traversed into
16+
* since they're rarely traversed into.
17+
*
18+
* This interface is extensible, if you want to add your
19+
* own shallow types.
1720
*/
18-
export type ProjectionPathIgnoreTypes =
19-
| { _type: "reference" }
20-
| { _type: "block" };
21+
export interface ProjectionPathShallowTypes {
22+
"reference blocks": { _type: "reference" };
23+
"portable text blocks": { _type: "block" };
24+
}
25+
26+
type ShouldBeShallow<Value, CurrentPath> =
27+
// When at the top-level, we should allow deep nesting:
28+
CurrentPath extends ""
29+
? false
30+
: // These types should not be deeply-traversed:
31+
NonNullable<Value> extends ValueOf<ProjectionPathShallowTypes>
32+
? true
33+
: false;
2134

2235
/**
2336
* These simple types are shallow, and do not need to
2437
* include any deeper entries.
2538
*/
26-
type IsShallowType<Value> = IsAny<Value> extends true
39+
type IsSimpleType<Value> = IsAny<Value> extends true
2740
? true
2841
: IsNever<Value> extends true
2942
? true
3043
: Value extends Primitive
3144
? true
32-
: Value extends ProjectionPathIgnoreTypes
33-
? true
3445
: false;
3546

3647
/**
@@ -58,7 +69,9 @@ export type ProjectionPathEntries<Value> = IsAny<Value> extends true
5869
type _ProjectionPathEntries<
5970
CurrentPath extends string,
6071
Value
61-
> = IsShallowType<Value> extends true
72+
> = IsSimpleType<Value> extends true
73+
? never
74+
: ShouldBeShallow<Value, CurrentPath> extends true
6275
? never
6376
: Value extends { _type: "slug" }
6477
? Record<JoinPath<CurrentPath, "current">, string>

0 commit comments

Comments
 (0)