@sanity/codegen
Version:
Codegen toolkit for Sanity.io
563 lines (521 loc) • 15.5 kB
TypeScript
import { BooleanFlag } from '@oclif/core/interfaces';
import { CustomOptions } from '@oclif/core/interfaces';
import { DefinedTelemetryTrace } from '@sanity/telemetry';
import { ExprNode } from 'groq-js';
import { FSWatcher } from 'chokidar';
import { OptionFlag } from '@oclif/core/interfaces';
import { SanityCommand } from '@sanity/cli-core';
import { SchemaType } from 'groq-js';
import { spinner } from '@sanity/cli-core/ux';
import * as t from '@babel/types';
import { TransformOptions } from '@babel/core';
import { WorkerChannel } from '@sanity/worker-channels';
import { WorkerChannelReporter } from '@sanity/worker-channels';
import * as z from 'zod';
/**
* @deprecated use TypeGenConfig
*/
export declare type CodegenConfig = TypeGenConfig;
/**
* @internal
*/
export declare const configDefinition: z.ZodObject<{
formatGeneratedCode: z.ZodDefault<z.ZodBoolean>;
generates: z.ZodDefault<z.ZodString>;
overloadClientMethods: z.ZodDefault<z.ZodBoolean>;
path: z.ZodDefault<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString>]>>;
schema: z.ZodDefault<z.ZodString>;
}, z.core.$strip>;
/**
* A module containing queries that have been evaluated.
* @public
*/
export declare interface EvaluatedModule {
errors: (QueryEvaluationError | QueryExtractionError)[];
filename: string;
queries: EvaluatedQuery[];
}
/**
* An `ExtractedQuery` that has been evaluated against a schema, yielding a TypeScript type.
* @public
*/
export declare interface EvaluatedQuery extends ExtractedQuery {
ast: t.ExportNamedDeclaration;
code: string;
id: t.Identifier;
stats: TypeEvaluationStats;
tsType: t.TSType;
}
/**
* A module (file) containing extracted GROQ queries.
* @public
*/
export declare interface ExtractedModule {
errors: QueryExtractionError[];
filename: string;
queries: ExtractedQuery[];
}
/**
* A GROQ query extracted from a source file.
* @public
*/
export declare interface ExtractedQuery {
filename: string;
query: string;
variable: QueryVariable;
}
/**
* Filter a union of object types by the _type property. This is handy when working with page builder
* setups where the returned type is an array containing multiple types.
*
* @example
* ```ts
*
* export type Callout = {
* _type: 'callout'
* title?: string
* content?: string
* }
*
* export type Video = {
* _type: 'video'
* url?: string
* caption?: string
* }
* type FORM_QUERY_RESULT = {
* _id: string
* title?: string
* content?: Array<
* | ({ _key: string } & Callout)
* | ({ _key: string } & Video)
* >
* } | null
*
* // Get the type of the content with the Get helper
* type Content = Get<FORM_QUERY_RESULT, 'content', number>
*
* // Get the type for a callout module from the page builder type
* type CalloutModule = FilterByType<Content, 'callout'>
* // → { _key: string } & Callout
* ```
*/
export declare type FilterByType<U extends {
_type: string;
}, T extends U['_type']> = Extract<U, {
_type: T;
}>;
/**
* findQueriesInPath takes a path or array of paths and returns all GROQ queries in the files.
* @param path - The path or array of paths to search for queries
* @param babelOptions - The babel configuration to use when parsing the source
* @param resolver - A resolver function to use when resolving module imports
* @returns An async generator that yields the results of the search
* @beta
* @internal
*/
export declare function findQueriesInPath({ babelOptions, path, resolver, }: FindQueriesInPathOptions): {
files: string[];
queries: AsyncIterable<ExtractedModule>;
};
declare interface FindQueriesInPathOptions {
path: string | string[];
babelOptions?: TransformOptions;
resolver?: NodeJS.RequireResolve;
}
/**
* findQueriesInSource takes a source string and returns all GROQ queries in it.
* @param source - The source code to search for queries
* @param filename - The filename of the source code
* @param babelConfig - The babel configuration to use when parsing the source
* @param resolver - A resolver function to use when resolving module imports
* @returns
* @beta
* @internal
*/
export declare function findQueriesInSource(source: string, filename: string, babelConfig?: TransformOptions, resolver?: NodeJS.RequireResolve): ExtractedModule;
export declare interface GenerateTypesOptions {
schema: SchemaType;
overloadClientMethods?: boolean;
queries?: AsyncIterable<ExtractedModule>;
reporter?: WorkerChannelReporter<TypegenWorkerChannel>;
root?: string;
schemaPath?: string;
}
/**
* Result from a single generation run.
* @internal
*/
export declare interface GenerationResult {
code: string;
duration: number;
emptyUnionTypeNodesGenerated: number;
filesWithErrors: number;
outputSize: number;
queriesCount: number;
queryFilesCount: number;
schemaTypesCount: number;
typeNodesGenerated: number;
unknownTypeNodesGenerated: number;
unknownTypeNodesRatio: number;
}
/**
* Get a deeply nested property type from a complex type structure. Safely navigates
* through nullable types (`T | null | undefined`) at each level, preserving the
* nullability of the final accessed property.
*
* Supports up to 20 levels of nesting.
*
* @example
* ```ts
* type POST_QUERY_RESULT = {
* _id: string
* author: {
* profile: {
* name: string;
* } | null;
* } | null;
* links: Array<{
* _key: string
* type: 'link'
* label: string
* url: string
* }> | null
* } | null
*
* // Basic property access:
* type Id = Get<POST_QUERY_RESULT, '_id'>;
* // → string
*
* // Nested property access:
* type Profile = Get<POST_QUERY_RESULT, 'author', 'profile';
* // → { name: string } | null
*
* // Array element access using `number`:
* type Link = Get<POST_QUERY_RESULT, 'links', number, 'label'>;
* // → string
* ```
*/
export declare type Get<T, K1 extends keyof NonNullish<T>, K2 extends keyof NavigatePath<T, [K1]> = never, K3 extends keyof NavigatePath<T, [K1, K2]> = never, K4 extends keyof NavigatePath<T, [K1, K2, K3]> = never, K5 extends keyof NavigatePath<T, [K1, K2, K3, K4]> = never, K6 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5]> = never, K7 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6]> = never, K8 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6, K7]> = never, K9 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6, K7, K8]> = never, K10 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6, K7, K8, K9]> = never, K11 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6, K7, K8, K9, K10]> = never, K12 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11]> = never, K13 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, K12]> = never, K14 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, K12, K13]> = never, K15 extends keyof NavigatePath<T, [K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, K12, K13, K14]> = never, K16 extends keyof NavigatePath<T, [
K1,
K2,
K3,
K4,
K5,
K6,
K7,
K8,
K9,
K10,
K11,
K12,
K13,
K14,
K15
]> = never, K17 extends keyof NavigatePath<T, [
K1,
K2,
K3,
K4,
K5,
K6,
K7,
K8,
K9,
K10,
K11,
K12,
K13,
K14,
K15,
K16
]> = never, K18 extends keyof NavigatePath<T, [
K1,
K2,
K3,
K4,
K5,
K6,
K7,
K8,
K9,
K10,
K11,
K12,
K13,
K14,
K15,
K16,
K17
]> = never, K19 extends keyof NavigatePath<T, [
K1,
K2,
K3,
K4,
K5,
K6,
K7,
K8,
K9,
K10,
K11,
K12,
K13,
K14,
K15,
K16,
K17,
K18
]> = never, K20 extends keyof NavigatePath<T, [
K1,
K2,
K3,
K4,
K5,
K6,
K7,
K8,
K9,
K10,
K11,
K12,
K13,
K14,
K15,
K16,
K17,
K18,
K19
]> = never> = GetAtPath<T, TakeUntilNever<[
K1,
K2,
K3,
K4,
K5,
K6,
K7,
K8,
K9,
K10,
K11,
K12,
K13,
K14,
K15,
K16,
K17,
K18,
K19,
K20
]>>;
/** Recursively gets value at path, preserving nullability at final access. */
declare type GetAtPath<T, Path extends unknown[]> = Path extends [] ? T : Path extends [infer K] ? K extends keyof NonNullish<T> ? NonNullish<T>[K] : never : Path extends [infer K, ...infer Rest] ? K extends keyof NonNullish<T> ? GetAtPath<NonNullish<T>[K], Rest> : never : never;
/**
* This is a custom implementation of require.resolve that takes into account the paths
* configuration in tsconfig.json. This is necessary if we want to resolve paths that are
* custom defined in the tsconfig.json file.
* Resolving here is best effort and might not work in all cases.
* @beta
*/
export declare function getResolver(cwd?: string): NodeJS.RequireResolve;
/** Recursively navigates through a path, stripping nullability for key lookup. */
declare type NavigatePath<T, Path extends unknown[]> = Path extends [] ? NonNullish<T> : Path extends [infer K, ...infer Rest] ? K extends keyof NonNullish<T> ? NavigatePath<NonNullish<T>[K], Rest> : never : never;
/** Excludes `null` and `undefined` from a type. */
declare type NonNullish<T> = T extends null | undefined ? never : T;
/**
* An error that occurred during query evaluation.
* @public
*/
declare class QueryEvaluationError extends Error {
filename: string;
variable?: QueryVariable;
constructor({ cause, filename, variable }: QueryEvaluationErrorOptions);
}
declare interface QueryEvaluationErrorOptions {
cause: unknown;
filename: string;
variable?: QueryVariable;
}
/**
* An error that occurred during query extraction.
* @public
*/
export declare class QueryExtractionError extends Error {
filename: string;
variable?: QueryVariable;
constructor({ cause, filename, variable }: QueryExtractionErrorOptions);
}
declare interface QueryExtractionErrorOptions {
cause: unknown;
filename: string;
variable?: QueryVariable;
}
declare interface QueryVariable {
id: t.Identifier;
end?: number;
start?: number;
}
/**
* Read, parse and process a config file
* @internal
*/
export declare function readConfig(path: string): Promise<TypeGenConfig>;
/**
* Read a schema from a given path
* @param path - The path to the schema
* @returns The schema
* @internal
* @beta
**/
export declare function readSchema(path: string): Promise<SchemaType>;
/**
* Register Babel with the given options
*
* @param babelOptions - The options to use when registering Babel
* @beta
*/
export declare function registerBabel(babelOptions?: TransformOptions): void;
/**
* Runs a single typegen generation.
*
* This is the programmatic API for generating TypeScript types from GROQ queries.
* It spawns a worker thread to perform the generation and displays progress via CLI spinners.
*
* @param options - Configuration options including typegen config and working directory
* @returns Generation result containing the generated code and statistics
*/
export declare function runTypegenGenerate(options: RunTypegenOptions): Promise<GenerationResult>;
/**
* Options for running a single typegen generation.
* This is the programmatic API for one-off generation without file watching.
*/
export declare interface RunTypegenOptions {
/** Working directory (usually project root) */
workDir: string;
/** Typegen configuration */
config?: Partial<TypeGenConfig>;
/** Optional spinner instance for progress display */
spin?: ReturnType<typeof spinner>;
}
/**
* Starts a file watcher that triggers typegen on changes.
* Watches both query files (via patterns) and the schema JSON file.
* Implements debouncing and concurrency control to prevent multiple generations.
*/
export declare function runTypegenWatcher(options: RunTypegenOptions): {
getStats: () => WatcherStats;
stop: () => Promise<void>;
watcher: FSWatcher;
};
/**
* safeParseQuery parses a GROQ query string, but first attempts to extract any parameters used in slices. This method is _only_
* intended for use in type generation where we don't actually execute the parsed AST on a dataset, and should not be used elsewhere.
* @internal
*/
export declare function safeParseQuery(query: string): ExprNode;
/** Builds a tuple from elements, stopping at the first `never`. */
declare type TakeUntilNever<T extends unknown[]> = T extends [infer H, ...infer Rest] ? [H] extends [never] ? [] : [H, ...TakeUntilNever<Rest>] : [];
/**
* Statistics from the query type evaluation process.
* @public
*/
declare interface TypeEvaluationStats {
allTypes: number;
emptyUnions: number;
unknownTypes: number;
}
export declare type TypeGenConfig = z.infer<typeof configDefinition>;
/**
* A class used to generate TypeScript types from a given schema
* @beta
*/
export declare class TypeGenerator {
private getSchemaTypeGenerator;
private getSchemaTypeDeclarations;
private getAllSanitySchemaTypesDeclaration;
private getArrayOfDeclaration;
private getInternalReferenceSymbolDeclaration;
private static getEvaluatedModules;
private static getQueryMapDeclaration;
generateTypes(options: GenerateTypesOptions): Promise<{
ast: t.Program;
code: string;
}>;
}
/**
* @internal
*/
export declare class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerateCommand> {
static description: string;
static examples: {
command: string;
description: string;
}[];
static flags: {
'config-path': OptionFlag<string | undefined, CustomOptions>;
watch: BooleanFlag<boolean>;
};
run(): Promise<void>;
private getConfig;
private runSingle;
private runWatcher;
}
export declare const TypegenWatchModeTrace: DefinedTelemetryTrace<TypegenWatchModeTraceAttributes, void>;
/**
* Attributes for typegen watch mode trace - tracks the start and stop of watch mode
* sessions with statistics about generation runs.
*/
declare type TypegenWatchModeTraceAttributes = {
averageGenerationDuration: number;
generationFailedCount: number;
generationSuccessfulCount: number;
step: 'stopped';
watcherDuration: number;
} | {
step: 'started';
};
export declare type TypegenWorkerChannel = WorkerChannel.Definition<{
evaluatedModules: WorkerChannel.Stream<EvaluatedModule>;
generatedQueryTypes: WorkerChannel.Event<{
queryMapDeclaration: {
ast: t.Program;
code: string;
};
}>;
generatedSchemaTypes: WorkerChannel.Event<{
allSanitySchemaTypesDeclaration: {
ast: t.ExportNamedDeclaration;
code: string;
id: t.Identifier;
};
internalReferenceSymbol: {
ast: t.ExportNamedDeclaration;
code: string;
id: t.Identifier;
};
schemaTypeDeclarations: {
ast: t.ExportNamedDeclaration;
code: string;
id: t.Identifier;
name: string;
tsType: t.TSType;
}[];
}>;
}>;
export declare const TypesGeneratedTrace: DefinedTelemetryTrace<TypesGeneratedTraceAttributes, void>;
declare interface TypesGeneratedTraceAttributes {
configMethod: 'cli' | 'legacy';
configOverloadClientMethods: boolean;
emptyUnionTypeNodesGenerated: number;
filesWithErrors: number;
outputSize: number;
queriesCount: number;
queryFilesCount: number;
schemaTypesCount: number;
typeNodesGenerated: number;
unknownTypeNodesGenerated: number;
unknownTypeNodesRatio: number;
}
declare type WatcherStats = Omit<Extract<TypegenWatchModeTraceAttributes, {
step: 'stopped';
}>, 'step'>;
export { }