@sanity-codegen/core
The following is a sub-package of sanity-codegen.
This package includes APIs to programmatically generate types from GROQ.
👋 NOTE: You don't have to use this package directly. It's only meant for users who want to use sanity-codegen programmatically. The CLI is the preferred way to use Sanity Codegen and its setup and usage is much more streamlined.
Installation
# NOTE: the alpha is required at this time
npm install --save-dev @sanity-codegen/core@alpha
or
# NOTE: the alpha is required at this time
yarn add --dev @sanity-codegen/core@alpha
Usage
This package will parse TypeScript (or JavaScript) files for usages of @sanity-codegen/client and will return the resulting TypeScript in a single source file as a string.
In order for a GROQ query to plucked and parsed by this package, the query expression in code must match the following shape:
query('QueryKey', groq`*[_type == 'foo']`);
In particular:
- The query must be contained inside of a function call (any identifier can be used).
- The first argument of that function call must be a string literal. This string literal is called the query key. Query keys are used to uniquely identify a query and must start with a capital letter.
- The second argument must be a tagged template literal with the tag being exactly
groq.
- The template literal may not contain any expressions. If you need to use parameters, use GROQ parameters (e.g.
$parameter).
If any source file contains a groq query in the form described above, Sanity Codegen will attempt to parse it and generate TypeScript types for it.
The most simple way to use this package is via generateTypes.
Note: you may also need to extract the schema using the @sanity-codegen/extractor package.
import fs from 'fs';
import { generateTypes } from '@sanity-codegen/core';
import { schemaExtractor } from '@sanity-codegen/extractor';
async function main() {
const schema = await schemaExtractor({
sanityConfigPath: './studio/sanity.config.ts',
});
const codegenResult = await generateTypes({
include: ['./src/**/*.{js,ts,tsx}'],
exclude: ['**/*.test.{js,ts,tsx}', '**/node_modules'],
schema,
});
await fs.promises.writeFile('./query-types.d.ts', codegenResult);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
The result of the above code will look something like this:
query-types.d.ts
declare namespace Sanity {
namespace Queries {
type BookAuthor = {
name?: string;
} | null;
type BookTitles = (string | null)[];
type QueryMap = {
BookAuthor: BookAuthor;
BookTitles: BookTitles;
};
}
}
For each query found (in this case, just 2), a type will be created inside the namespace Sanity.Queries using the query key as the type name.
Each query type is also included in the QueryMap type. This type is utilized in the @sanity-codegen/client to match the query key provided in the client function call to the resulting generated type.
This orchestration between client and parser/plucker makes it so that query function calls will automatically reference the GROQ codegen result without needing to specify any types manually.
For example, if everything is set up correctly, the following call will be typed after the GROQ codegen runs.
import { sanity, groq } from './some-configured-client';
export async function someFunction() {
const bookAuthors = await sanity.query(
'BookAuthors',
groq`*[_type == 'book'].author`,
);
return bookAuthors;
}
Notice how there are no type annotations to be seen 😎. See the client docs for more details.
👋 NOTE: However, if you need to reference any query types, you can do so via the Sanity.Queries namespace, e.g. Sanity.Queries.BookAuthors.
- See here for more details on the schema code generator.
- See here for more details on the client.
Reference
This package contains also more granular functions to better fit your use case. See the functions below.
pluckGroqFromFiles()
export interface PluckGroqFromFilesOptions {
include: string | string[] | (() => Promise<string[]>);
exclude?: string | string[];
root?: string;
babelOptions?: Record<string, unknown>;
}
export declare function pluckGroqFromFiles(
options: PluckGroqFromFilesOptions,
): Promise<
{
queryKey: string;
query: string;
}[]
>;
pluckGroqFromSource()
export interface PluckGroqFromSourceOptions {
source: string;
filename: string;
groqTagName?: string;
babelOptions?: Record<string, unknown>;
resolvePluckedFile?: (request: string) => string | Promise<string>;
}
export declare function pluckGroqFromSource(
options: PluckGroqFromSourceOptions,
): {
queryKey: string;
query: string;
}[];
Expression support
It's not currently possible for the query plucker to execute any code that makes up your query. This is due to the different environment your code runs in vs what sanity-codegen runs in. Sanity-codegen runs in node.js and your code most likely runs in the browser. We try not to make assumptions on environments so instead of executing the code that makes up your query, we statically pluck and combine the different expressions into one string from many different files.
This means, in order to pluck a query from your source code, it must be made of entirely static expressions.
For example, the following is allowed:
const type = 'book';
const projection = `
{
'name': title,
'description': summary,
}
`;
const myQuery = groq`
*[_type == '${type}' && _id == $id] ${projection}
`;
export function getEntity(id) {
return sanity.query('QueryKey', myQuery, { id });
}
Notice how there are no dynamic expressions where javascript needs to be executed in order to pluck the query.
If you do need to use dynamic variables, use GROQ parameters and feed those in as the 3rd argument of sanity.query.
generateTypes()
interface GenerateTypesOptions extends PluckGroqFromFilesOptions {
prettierResolveConfigPath?: string;
prettierResolveConfigOptions?: ResolveConfigOptions;
normalizedSchema: Sanity.SchemaDef.Schema;
}
export declare function generateTypes(
options: GenerateTypesOptions,
): Promise<string>;
Sanity.GroqCodegen.StructureNode
The following StructureNode is used in the rest of the APIs.
declare namespace Sanity {
namespace GroqCodegen {
type StructureNode =
| LazyNode
| AndNode
| OrNode
| ArrayNode
| ObjectNode
| StringNode
| NumberNode
| BooleanNode
| ReferenceNode
| UnknownNode;
type LazyNode = {
type: 'Lazy';
get: () => StructureNode;
hash: string;
};
type AndNode = {
type: 'And';
children: StructureNode[];
hash: string;
};
type OrNode = {
type: 'Or';
children: StructureNode[];
hash: string;
};
type ArrayNode = {
type: 'Array';
canBeNull: boolean;
canBeOptional: boolean;
of: StructureNode;
hash: string;
};
type ObjectNode = {
type: 'Object';
properties: Array<{ key: string; value: StructureNode }>;
canBeNull: boolean;
canBeOptional: boolean;
hash: string;
};
type StringNode = {
type: 'String';
canBeNull: boolean;
canBeOptional: boolean;
value: string | null;
hash: string;
};
type NumberNode = {
type: 'Number';
canBeNull: boolean;
canBeOptional: boolean;
value: number | null;
hash: string;
};
type BooleanNode = {
type: 'Boolean';
canBeNull: boolean;
canBeOptional: boolean;
hash: string;
};
type ReferenceNode = {
type: 'Reference';
to: StructureNode;
canBeNull: boolean;
canBeOptional: boolean;
hash: string;
};
type UnknownNode = { type: 'Unknown'; hash: 'unknown' };
}
}
transformSchemaToStructure()
export interface TransformSchemaToStructureOptions {
normalizedSchema: Sanity.SchemaDef.Schema;
}
export declare function transformSchemaToStructure(
options: TransformSchemaToStructureOptions,
): Sanity.GroqCodegen.StructureNode;
transformGroqToStructure()
export interface TransformGroqToStructureOptions {
node: Groq.ExprNode;
normalizedSchema: Sanity.SchemaDef.Schema;
scopes: Sanity.GroqCodegen.StructureNode[];
}
export declare function transformGroqToStructure(
options: TransformGroqToStructureOptions,
): Sanity.GroqCodegen.StructureNode;
transformStructureToTs()
export interface TransformStructureToTsOptions {
structure: Sanity.GroqCodegen.StructureNode;
}
export declare function transformStructureToTs(
options: TransformStructureToTsOptions,
): { query: TSType; references: Record<string, TSType> };