UNPKG

@sanity/codegen

Version:

Codegen toolkit for Sanity.io

563 lines (521 loc) 15.5 kB
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 { }