diff --git a/workspaces/leetcode-api/graphql-codegen.config.ts b/workspaces/leetcode-api/graphql-codegen.config.ts index f99ae76a..a22cbb04 100644 --- a/workspaces/leetcode-api/graphql-codegen.config.ts +++ b/workspaces/leetcode-api/graphql-codegen.config.ts @@ -1,6 +1,8 @@ import type { CodegenConfig } from "@graphql-codegen/cli"; +import type { Types as GraphQLCodegen } from "@graphql-codegen/plugin-helpers"; +import immutableUpdate from "immutability-helper"; -const commonTypeScriptPluginConfig = { +const commonTypeScriptPluginConfig: GraphQLCodegen.PluginConfig = { arrayInputCoercion: false, enumsAsTypes: true, defaultScalarType: "unknown", @@ -10,50 +12,91 @@ const commonTypeScriptPluginConfig = { // TODO: add strictScalars: true }; -const headerPlugin = { +const headerPlugin: GraphQLCodegen.OutputConfig = { add: { content: ` // THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! // Instead, update the generation process or inputs and run \`yarn codegen\`. `, + placement: "prepend", + }, +}; + +const nearOperationFilePreset: GraphQLCodegen.ConfiguredOutput = { + preset: "near-operation-file", + presetConfig: { + // We enable `globalNamespace` below so that we don't import the base + // types through the preset, but let the specified plugins add the imports + // if necessary. This allows us to make sure the base types import is + // below the header, and it uses the ".ts" extension. + baseTypesPath: "", + + extension: ".generated.ts", + fileName: null as string | null, + }, + plugins: [headerPlugin], + config: { + ...commonTypeScriptPluginConfig, + + // Specified plugins will handle the imports and exports! + globalNamespace: true, + noExport: true, }, }; const config: CodegenConfig = { schema: "schema.graphql", - documents: ["src/**/*.graphql"], + documents: ["src/api/**/query.graphql"], overwrite: true, emitLegacyCommonJSImports: false, generates: { + // Generate the base types file! "src/graphqlTypes.generated.ts": { plugins: [headerPlugin, "typescript"], config: commonTypeScriptPluginConfig, }, - "src/": { - preset: "near-operation-file", - presetConfig: { - // The base types are not imported because of the use of - // `globalNamespace` below, instead our custom plugin will add the - // import, so that it can end up _below_ the header. - baseTypesPath: "", - - extension: ".generated.ts", - fileName: "fetchGraphQL", + + // Generate a file for the query variables and result types, near each + // operation. + "src/api/**/queryTypes.generated.ts": immutableUpdate( + nearOperationFilePreset, + { + presetConfig: { fileName: { $set: "queryTypes" } }, + plugins: { + $push: [ + // Explicitly add the base types import, since the preset was + // tricked into not adding it. + { + add: { + content: + '\n\nimport type * as Types from "../../graphqlTypes.generated.ts";\n\n', + placement: "content", + }, + }, + + // Generate TypeScript operations types, overriding the + // `globalNamespace` which was set in the preset. + { "typescript-operations": { globalNamespace: false } }, + ], + }, + + // Do export the types. + config: { noExport: { $set: false } }, }, - plugins: [ - headerPlugin, - { "typescript-operations": { globalNamespace: false } }, - "./src/scripts/codegen/graphqlCodegenPlugin.ts", - ], - config: { - ...commonTypeScriptPluginConfig, - - // Our custom plugin will handle the imports and exports! - globalNamespace: true, - noExport: true, + ), + + // Generate a small SDK for each operation using our custom plugin. + "src/api/**/fetchGraphQL.generated.ts": immutableUpdate( + nearOperationFilePreset, + { + presetConfig: { fileName: { $set: "fetchGraphQL" } }, + plugins: { + $push: ["./src/scripts/codegen/graphqlCodegenPlugin.ts"], + }, }, - }, + ), }, + hooks: { afterAllFileWrite: ["prettier --write"], }, diff --git a/workspaces/leetcode-api/package.json b/workspaces/leetcode-api/package.json index 1d976493..abf49918 100644 --- a/workspaces/leetcode-api/package.json +++ b/workspaces/leetcode-api/package.json @@ -42,11 +42,12 @@ "cross-env": "7.0.3", "eslint": "9.12.0", "graphql-query-compress": "1.2.4", + "immutability-helper": "patch:immutability-helper@npm%3A3.1.1#~/.yarn/patches/immutability-helper-npm-3.1.1-482f1f8f58.patch", "jest": "29.7.0", "prettier": "3.3.3", "ts-jest": "29.2.5", + "ts-to-zod": "3.13.0", "tsx": "4.19.1", - "type-fest": "4.26.1", "typescript": "5.6.2" } } diff --git a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/fetchGraphQL.generated.ts b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/fetchGraphQL.generated.ts index 4169d440..ecf363cc 100644 --- a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/fetchGraphQL.generated.ts +++ b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/fetchGraphQL.generated.ts @@ -1,34 +1,44 @@ // THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! // Instead, update the generation process or inputs and run `yarn codegen`. -import type { Simplify } from "type-fest"; +import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; -import type * as Types from "../../graphqlTypes.generated.ts"; - -type ActiveDailyCodingChallengeQuestionQueryVariables = Types.Exact<{ - [key: string]: never; -}>; - -type ActiveDailyCodingChallengeQuestionQuery = { - activeDailyCodingChallengeQuestion?: { - date: unknown; - question: { - difficulty?: string | null; - questionFrontendId?: string | null; - title: string; - titleSlug: string; - }; - } | null; -}; - -export type QueryVariables = - Simplify; -export type Query = Simplify; +import type { + ActiveDailyCodingChallengeQuestionQuery as OriginalQueryResult, + ActiveDailyCodingChallengeQuestionQueryVariables as OriginalQueryVariables, +} from "./queryTypes.generated.ts"; export const QUERY = "query{activeDailyCodingChallengeQuestion{date question{difficulty questionFrontendId title titleSlug}}}"; -export function fetchGraphQL(variables: QueryVariables): Promise { - return getGraphQLClient().request(QUERY, variables); +export const queryResultZodType = z.object({ + activeDailyCodingChallengeQuestion: z + .object({ + date: z.unknown(), + question: z.object({ + difficulty: z.string().optional().nullable(), + questionFrontendId: z.string().optional().nullable(), + title: z.string(), + titleSlug: z.string(), + }), + }) + .optional() + .nullable(), +}); + +export type QueryResult = z.infer; +export type QueryVariables = OriginalQueryVariables; + +export async function fetchGraphQL( + variables: QueryVariables, +): Promise { + const untrustedData = await getGraphQLClient().request(QUERY, variables); + + // The type annotation serves as a TypeScript assert that the generated + // Zod type is compatible with the types generated by GraphQL Codegen. + const validatedData: OriginalQueryResult = + queryResultZodType.parse(untrustedData); + + return validatedData; } diff --git a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/main.ts b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/main.ts index 8c63a785..3722026b 100644 --- a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/main.ts +++ b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/main.ts @@ -16,17 +16,13 @@ const questionZodType = z.object({ titleSlug: questionTitleSlugZodType, }); -const activeDailyCodingChallengeQuestionZodType = z - .object({ - activeDailyCodingChallengeQuestion: z.object({ - date: z - .string() - .trim() - .regex(/^\d{4}-\d{2}-\d{2}$/), - question: questionZodType, - }), - }) - .transform((data) => data.activeDailyCodingChallengeQuestion); +const activeDailyCodingChallengeQuestionZodType = z.object({ + date: z + .string() + .trim() + .regex(/^\d{4}-\d{2}-\d{2}$/), + question: questionZodType, +}); export type ActiveDailyCodingChallengeQuestion = z.infer< typeof activeDailyCodingChallengeQuestionZodType @@ -34,9 +30,11 @@ export type ActiveDailyCodingChallengeQuestion = z.infer< export async function fetchActiveDailyCodingChallengeQuestionWithoutDateValidation(): Promise { // TODO: have a way to omit variables when there aren't any - const data = await fetchGraphQL({}); + const { activeDailyCodingChallengeQuestion } = await fetchGraphQL({}); - return activeDailyCodingChallengeQuestionZodType.parse(data); + return activeDailyCodingChallengeQuestionZodType.parse( + activeDailyCodingChallengeQuestion, + ); } export async function fetchActiveDailyCodingChallengeQuestionWithDateValidation({ diff --git a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/queryTypes.generated.ts b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/queryTypes.generated.ts new file mode 100644 index 00000000..d814608f --- /dev/null +++ b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/queryTypes.generated.ts @@ -0,0 +1,20 @@ +// THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! +// Instead, update the generation process or inputs and run `yarn codegen`. + +import type * as Types from "../../graphqlTypes.generated.ts"; + +export type ActiveDailyCodingChallengeQuestionQueryVariables = Types.Exact<{ + [key: string]: never; +}>; + +export type ActiveDailyCodingChallengeQuestionQuery = { + activeDailyCodingChallengeQuestion?: { + date?: unknown; + question: { + difficulty?: string | null; + questionFrontendId?: string | null; + title: string; + titleSlug: string; + }; + } | null; +}; diff --git a/workspaces/leetcode-api/src/api/question-list/fetchGraphQL.generated.ts b/workspaces/leetcode-api/src/api/question-list/fetchGraphQL.generated.ts index 06ca7503..721259c1 100644 --- a/workspaces/leetcode-api/src/api/question-list/fetchGraphQL.generated.ts +++ b/workspaces/leetcode-api/src/api/question-list/fetchGraphQL.generated.ts @@ -1,37 +1,47 @@ // THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! // Instead, update the generation process or inputs and run `yarn codegen`. -import type { Simplify } from "type-fest"; +import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; -import type * as Types from "../../graphqlTypes.generated.ts"; - -type QuestionListQueryVariables = Types.Exact<{ - categorySlug: Types.Scalars["String"]["input"]; - limit?: Types.InputMaybe; - skip?: Types.InputMaybe; - filters: Types.QuestionListFilterInput; -}>; - -type QuestionListQuery = { - questionList?: { - totalNum: number; - data: Array<{ - difficulty?: string | null; - isPaidOnly?: boolean | null; - questionFrontendId?: string | null; - title: string; - titleSlug: string; - }>; - } | null; -}; - -export type QueryVariables = Simplify; -export type Query = Simplify; +import type { + QuestionListQuery as OriginalQueryResult, + QuestionListQueryVariables as OriginalQueryVariables, +} from "./queryTypes.generated.ts"; export const QUERY = "query($categorySlug:String!,$limit:Int,$skip:Int,$filters:QuestionListFilterInput!){questionList(categorySlug:$categorySlug limit:$limit skip:$skip filters:$filters){data{difficulty isPaidOnly questionFrontendId title titleSlug}totalNum}}"; -export function fetchGraphQL(variables: QueryVariables): Promise { - return getGraphQLClient().request(QUERY, variables); +export const queryResultZodType = z.object({ + questionList: z + .object({ + totalNum: z.number(), + data: z.array( + z.object({ + difficulty: z.string().optional().nullable(), + isPaidOnly: z.boolean().optional().nullable(), + questionFrontendId: z.string().optional().nullable(), + title: z.string(), + titleSlug: z.string(), + }), + ), + }) + .optional() + .nullable(), +}); + +export type QueryResult = z.infer; +export type QueryVariables = OriginalQueryVariables; + +export async function fetchGraphQL( + variables: QueryVariables, +): Promise { + const untrustedData = await getGraphQLClient().request(QUERY, variables); + + // The type annotation serves as a TypeScript assert that the generated + // Zod type is compatible with the types generated by GraphQL Codegen. + const validatedData: OriginalQueryResult = + queryResultZodType.parse(untrustedData); + + return validatedData; } diff --git a/workspaces/leetcode-api/src/api/question-list/main.ts b/workspaces/leetcode-api/src/api/question-list/main.ts index 39c0d592..bda40b92 100644 --- a/workspaces/leetcode-api/src/api/question-list/main.ts +++ b/workspaces/leetcode-api/src/api/question-list/main.ts @@ -18,12 +18,10 @@ export type QuestionListQuestion = z.infer; const questionListZodType = z .object({ - questionList: z.object({ - data: z.array(questionZodType), - totalNum: z.number().int().nonnegative(), - }), + data: z.array(questionZodType), + totalNum: z.number().int().nonnegative(), }) - .transform(({ questionList: { data, totalNum } }) => ({ + .transform(({ data, totalNum }) => ({ questions: data, totalNum, })); @@ -52,12 +50,12 @@ export async function fetchQuestionList({ limit: number; skip: number; }): Promise { - const data = await fetchGraphQL({ + const { questionList } = await fetchGraphQL({ categorySlug, filters, limit, skip, }); - return questionListZodType.parse(data); + return questionListZodType.parse(questionList); } diff --git a/workspaces/leetcode-api/src/api/question-list/queryTypes.generated.ts b/workspaces/leetcode-api/src/api/question-list/queryTypes.generated.ts new file mode 100644 index 00000000..fbdf3f2b --- /dev/null +++ b/workspaces/leetcode-api/src/api/question-list/queryTypes.generated.ts @@ -0,0 +1,24 @@ +// THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! +// Instead, update the generation process or inputs and run `yarn codegen`. + +import type * as Types from "../../graphqlTypes.generated.ts"; + +export type QuestionListQueryVariables = Types.Exact<{ + categorySlug: Types.Scalars["String"]["input"]; + limit?: Types.InputMaybe; + skip?: Types.InputMaybe; + filters: Types.QuestionListFilterInput; +}>; + +export type QuestionListQuery = { + questionList?: { + totalNum: number; + data: Array<{ + difficulty?: string | null; + isPaidOnly?: boolean | null; + questionFrontendId?: string | null; + title: string; + titleSlug: string; + }>; + } | null; +}; diff --git a/workspaces/leetcode-api/src/api/recent-ac-submission-list/fetchGraphQL.generated.ts b/workspaces/leetcode-api/src/api/recent-ac-submission-list/fetchGraphQL.generated.ts index 948ca991..deb0bace 100644 --- a/workspaces/leetcode-api/src/api/recent-ac-submission-list/fetchGraphQL.generated.ts +++ b/workspaces/leetcode-api/src/api/recent-ac-submission-list/fetchGraphQL.generated.ts @@ -1,31 +1,43 @@ // THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! // Instead, update the generation process or inputs and run `yarn codegen`. -import type { Simplify } from "type-fest"; +import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; -import type * as Types from "../../graphqlTypes.generated.ts"; +import type { + RecentAcSubmissionListQuery as OriginalQueryResult, + RecentAcSubmissionListQueryVariables as OriginalQueryVariables, +} from "./queryTypes.generated.ts"; -type RecentAcSubmissionListQueryVariables = Types.Exact<{ - username: Types.Scalars["String"]["input"]; - limit: Types.Scalars["Int"]["input"]; -}>; +export const QUERY = + "query($username:String!,$limit:Int!){recentAcSubmissionList(username:$username,limit:$limit){id title titleSlug timestamp}}"; -type RecentAcSubmissionListQuery = { - recentAcSubmissionList?: Array<{ - id?: string | null; - title?: string | null; - titleSlug?: string | null; - timestamp?: string | null; - }> | null; -}; +export const queryResultZodType = z.object({ + recentAcSubmissionList: z + .array( + z.object({ + id: z.string().optional().nullable(), + title: z.string().optional().nullable(), + titleSlug: z.string().optional().nullable(), + timestamp: z.string().optional().nullable(), + }), + ) + .optional() + .nullable(), +}); -export type QueryVariables = Simplify; -export type Query = Simplify; +export type QueryResult = z.infer; +export type QueryVariables = OriginalQueryVariables; -export const QUERY = - "query($username:String!,$limit:Int!){recentAcSubmissionList(username:$username,limit:$limit){id title titleSlug timestamp}}"; +export async function fetchGraphQL( + variables: QueryVariables, +): Promise { + const untrustedData = await getGraphQLClient().request(QUERY, variables); + + // The type annotation serves as a TypeScript assert that the generated + // Zod type is compatible with the types generated by GraphQL Codegen. + const validatedData: OriginalQueryResult = + queryResultZodType.parse(untrustedData); -export function fetchGraphQL(variables: QueryVariables): Promise { - return getGraphQLClient().request(QUERY, variables); + return validatedData; } diff --git a/workspaces/leetcode-api/src/api/recent-ac-submission-list/main.ts b/workspaces/leetcode-api/src/api/recent-ac-submission-list/main.ts index 2c95b6d1..bdabf294 100644 --- a/workspaces/leetcode-api/src/api/recent-ac-submission-list/main.ts +++ b/workspaces/leetcode-api/src/api/recent-ac-submission-list/main.ts @@ -20,11 +20,7 @@ const submissionZodType = z.object({ export type RecentAcSubmission = z.infer; -const recentAcSubmissionListZodType = z - .object({ - recentAcSubmissionList: z.array(submissionZodType), - }) - .transform((data) => data.recentAcSubmissionList); +const recentAcSubmissionListZodType = z.array(submissionZodType); export async function fetchRecentAcSubmissionList({ limit = 50, @@ -33,7 +29,7 @@ export async function fetchRecentAcSubmissionList({ limit?: number; username: string; }): Promise { - const data = await fetchGraphQL({ username, limit }); + const { recentAcSubmissionList } = await fetchGraphQL({ username, limit }); - return recentAcSubmissionListZodType.parse(data); + return recentAcSubmissionListZodType.parse(recentAcSubmissionList); } diff --git a/workspaces/leetcode-api/src/api/recent-ac-submission-list/queryTypes.generated.ts b/workspaces/leetcode-api/src/api/recent-ac-submission-list/queryTypes.generated.ts new file mode 100644 index 00000000..309e890e --- /dev/null +++ b/workspaces/leetcode-api/src/api/recent-ac-submission-list/queryTypes.generated.ts @@ -0,0 +1,18 @@ +// THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! +// Instead, update the generation process or inputs and run `yarn codegen`. + +import type * as Types from "../../graphqlTypes.generated.ts"; + +export type RecentAcSubmissionListQueryVariables = Types.Exact<{ + username: Types.Scalars["String"]["input"]; + limit: Types.Scalars["Int"]["input"]; +}>; + +export type RecentAcSubmissionListQuery = { + recentAcSubmissionList?: Array<{ + id?: string | null; + title?: string | null; + titleSlug?: string | null; + timestamp?: string | null; + }> | null; +}; diff --git a/workspaces/leetcode-api/src/api/topic/fetchGraphQL.generated.ts b/workspaces/leetcode-api/src/api/topic/fetchGraphQL.generated.ts index ed3a04d0..0752204f 100644 --- a/workspaces/leetcode-api/src/api/topic/fetchGraphQL.generated.ts +++ b/workspaces/leetcode-api/src/api/topic/fetchGraphQL.generated.ts @@ -1,29 +1,48 @@ // THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! // Instead, update the generation process or inputs and run `yarn codegen`. -import type { Simplify } from "type-fest"; +import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; -import type * as Types from "../../graphqlTypes.generated.ts"; +import type { + TopicQuery as OriginalQueryResult, + TopicQueryVariables as OriginalQueryVariables, +} from "./queryTypes.generated.ts"; -type TopicQueryVariables = Types.Exact<{ - topicId: Types.Scalars["Int"]["input"]; -}>; +export const QUERY = + "query($topicId:Int!){topic(id:$topicId){title solutionTags{slug}post{content}}}"; -type TopicQuery = { - topic?: { - title: string; - solutionTags: Array<{ slug: string } | null>; - post: { content: string }; - } | null; -}; +export const queryResultZodType = z.object({ + topic: z + .object({ + title: z.string(), + solutionTags: z.array( + z + .object({ + slug: z.string(), + }) + .nullable(), + ), + post: z.object({ + content: z.string(), + }), + }) + .optional() + .nullable(), +}); -export type QueryVariables = Simplify; -export type Query = Simplify; +export type QueryResult = z.infer; +export type QueryVariables = OriginalQueryVariables; -export const QUERY = - "query($topicId:Int!){topic(id:$topicId){title solutionTags{slug}post{content}}}"; +export async function fetchGraphQL( + variables: QueryVariables, +): Promise { + const untrustedData = await getGraphQLClient().request(QUERY, variables); + + // The type annotation serves as a TypeScript assert that the generated + // Zod type is compatible with the types generated by GraphQL Codegen. + const validatedData: OriginalQueryResult = + queryResultZodType.parse(untrustedData); -export function fetchGraphQL(variables: QueryVariables): Promise { - return getGraphQLClient().request(QUERY, variables); + return validatedData; } diff --git a/workspaces/leetcode-api/src/api/topic/main.ts b/workspaces/leetcode-api/src/api/topic/main.ts index aa02b40f..2679a545 100644 --- a/workspaces/leetcode-api/src/api/topic/main.ts +++ b/workspaces/leetcode-api/src/api/topic/main.ts @@ -4,19 +4,13 @@ import { fetchGraphQL } from "./fetchGraphQL.generated.ts"; // TODO: see if there are any fun GraphQL ESLint plugins -const communitySolutionTopicZodType = z - .object({ - topic: z.object({ - title: z.string().trim(), - solutionTags: z.array( - z.object({ slug: z.string() }).transform(({ slug }) => slug), - ), - post: z - .object({ content: z.string() }) - .transform(({ content }) => content), - }), - }) - .transform((data) => data.topic); +const communitySolutionTopicZodType = z.object({ + title: z.string().trim(), + solutionTags: z.array( + z.object({ slug: z.string() }).transform(({ slug }) => slug), + ), + post: z.object({ content: z.string() }).transform(({ content }) => content), +}); export type CommunitySolutionTopic = z.infer< typeof communitySolutionTopicZodType @@ -25,8 +19,10 @@ export type CommunitySolutionTopic = z.infer< export async function fetchCommunitySolutionTopic( topicId: string, ): Promise { - // TODO: don't lie about the type - const data = await fetchGraphQL({ topicId: topicId as unknown as number }); + const { topic } = await fetchGraphQL({ + // TODO: don't lie about the type + topicId: topicId as unknown as number, + }); - return communitySolutionTopicZodType.parse(data); + return communitySolutionTopicZodType.parse(topic); } diff --git a/workspaces/leetcode-api/src/api/topic/queryTypes.generated.ts b/workspaces/leetcode-api/src/api/topic/queryTypes.generated.ts new file mode 100644 index 00000000..10923f2f --- /dev/null +++ b/workspaces/leetcode-api/src/api/topic/queryTypes.generated.ts @@ -0,0 +1,16 @@ +// THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! +// Instead, update the generation process or inputs and run `yarn codegen`. + +import type * as Types from "../../graphqlTypes.generated.ts"; + +export type TopicQueryVariables = Types.Exact<{ + topicId: Types.Scalars["Int"]["input"]; +}>; + +export type TopicQuery = { + topic?: { + title: string; + solutionTags: Array<{ slug: string } | null>; + post: { content: string }; + } | null; +}; diff --git a/workspaces/leetcode-api/src/scripts/codegen/createTypeScriptSourceFile.ts b/workspaces/leetcode-api/src/scripts/codegen/createTypeScriptSourceFile.ts new file mode 100644 index 00000000..ff175d85 --- /dev/null +++ b/workspaces/leetcode-api/src/scripts/codegen/createTypeScriptSourceFile.ts @@ -0,0 +1,8 @@ +import { createSourceFile, ScriptTarget, type SourceFile } from "typescript"; + +export function createTypeScriptSourceFile( + sourceText: string = "", +): SourceFile { + // TODO: share this constant with the adventure pack? + return createSourceFile("not/a/real/file", sourceText, ScriptTarget.ESNext); +} diff --git a/workspaces/leetcode-api/src/scripts/codegen/generateQueryResultZodType.ts b/workspaces/leetcode-api/src/scripts/codegen/generateQueryResultZodType.ts new file mode 100644 index 00000000..843938cf --- /dev/null +++ b/workspaces/leetcode-api/src/scripts/codegen/generateQueryResultZodType.ts @@ -0,0 +1,28 @@ +import { generateZodSchemaVariableStatement } from "ts-to-zod"; +import { isTypeAliasDeclaration } from "typescript"; + +import { only } from "@code-chronicles/util/only"; + +import { getCodeForTypeScriptNode } from "./getCodeForTypeScriptNode.ts"; +import { readTypeScriptSourceFile } from "./readTypeScriptSourceFile.ts"; + +export async function generateQueryResultZodType( + filePath: string, +): Promise { + // TODO: Unfortunately this ends up reading the _old_ version of this file, not the one after the codegen + const sourceFile = await readTypeScriptSourceFile(filePath); + const queryNode = only( + sourceFile.statements + .filter(isTypeAliasDeclaration) + .filter((statement) => statement.name.text.endsWith("Query")), + ); + + const { statement } = generateZodSchemaVariableStatement({ + node: queryNode, + sourceFile, + varName: "queryResultZodType", + customJSDocFormatTypes: {}, + }); + + return getCodeForTypeScriptNode(statement); +} diff --git a/workspaces/leetcode-api/src/scripts/codegen/getCodeForTypeScriptNode.ts b/workspaces/leetcode-api/src/scripts/codegen/getCodeForTypeScriptNode.ts new file mode 100644 index 00000000..c504884c --- /dev/null +++ b/workspaces/leetcode-api/src/scripts/codegen/getCodeForTypeScriptNode.ts @@ -0,0 +1,11 @@ +import { createPrinter, EmitHint, type Statement } from "typescript"; + +import { createTypeScriptSourceFile } from "./createTypeScriptSourceFile.ts"; + +export function getCodeForTypeScriptNode(statement: Statement): string { + return createPrinter().printNode( + EmitHint.Unspecified, + statement, + createTypeScriptSourceFile(""), + ); +} diff --git a/workspaces/leetcode-api/src/scripts/codegen/graphqlCodegenPlugin.ts b/workspaces/leetcode-api/src/scripts/codegen/graphqlCodegenPlugin.ts index 00d86614..07a8e75f 100644 --- a/workspaces/leetcode-api/src/scripts/codegen/graphqlCodegenPlugin.ts +++ b/workspaces/leetcode-api/src/scripts/codegen/graphqlCodegenPlugin.ts @@ -1,3 +1,5 @@ +import path from "node:path"; + import type { PluginFunction } from "@graphql-codegen/plugin-helpers"; import graphqlQueryCompress from "graphql-query-compress"; import invariant from "invariant"; @@ -8,6 +10,8 @@ import { isObject } from "@code-chronicles/util/isObject"; import { only } from "@code-chronicles/util/only"; import { spliceString } from "@code-chronicles/util/spliceString"; +import { generateQueryResultZodType } from "./generateQueryResultZodType.ts"; + const nonNegativeIntZodType = z.number().int().nonnegative(); const operationNameZodType = z.object({ @@ -15,10 +19,13 @@ const operationNameZodType = z.object({ loc: z.object({ start: nonNegativeIntZodType, end: nonNegativeIntZodType }), }); -export const plugin: PluginFunction<{}> = function plugin(_schema, documents) { +export const plugin: PluginFunction<{}> = async function plugin( + _schema, + documents, +) { // Encode some assumptions as invariants, namely that there is a single query // operation in the file. - const { document, rawSDL: unminifiedGraphQL } = only(documents); + const { document, rawSDL: unminifiedGraphQL, location } = only(documents); const definition = only(nullthrows(document).definitions); invariant( isObject(definition) && @@ -27,6 +34,16 @@ export const plugin: PluginFunction<{}> = function plugin(_schema, documents) { "Expected a query!", ); + // TODO: should we call it a Zod schema instead? + + // Generate a Zod type to parse the result! + const queryTypesFile = path.join( + path.dirname(nullthrows(location)), + "queryTypes.generated.ts", + ); + const queryResultZodTypeCode = + await generateQueryResultZodType(queryTypesFile); + // Extract the operation name from the definition, as well as information // about its location in the raw, unminified GraphQL. const { @@ -51,21 +68,32 @@ export const plugin: PluginFunction<{}> = function plugin(_schema, documents) { return { prepend: [ ` - import type { Simplify } from "type-fest"; + import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; - import type * as Types from "../../graphqlTypes.generated.ts"; + import type { + ${operationName}Query as OriginalQueryResult, + ${operationName}QueryVariables as OriginalQueryVariables, + } from "./queryTypes.generated.ts"; `, ], append: [ ` - export type QueryVariables = Simplify<${operationName}QueryVariables>; - export type Query = Simplify<${operationName}Query>; - export const QUERY = ${JSON.stringify(minifiedGraphQL)}; - export function fetchGraphQL(variables: QueryVariables): Promise { - return getGraphQLClient().request(QUERY, variables); + ${queryResultZodTypeCode} + + export type QueryResult = z.infer; + export type QueryVariables = OriginalQueryVariables; + + export async function fetchGraphQL(variables: QueryVariables): Promise { + const untrustedData = await getGraphQLClient().request(QUERY, variables); + + // The type annotation serves as a TypeScript assert that the generated + // Zod type is compatible with the types generated by GraphQL Codegen. + const validatedData: OriginalQueryResult = queryResultZodType.parse(untrustedData); + + return validatedData; } `, ], diff --git a/workspaces/leetcode-api/src/scripts/codegen/readTypeScriptSourceFile.ts b/workspaces/leetcode-api/src/scripts/codegen/readTypeScriptSourceFile.ts new file mode 100644 index 00000000..0d6fe01b --- /dev/null +++ b/workspaces/leetcode-api/src/scripts/codegen/readTypeScriptSourceFile.ts @@ -0,0 +1,14 @@ +import { readFile } from "node:fs/promises"; + +import type { SourceFile } from "typescript"; + +import { createTypeScriptSourceFile } from "./createTypeScriptSourceFile.ts"; + +export async function readTypeScriptSourceFile( + path: string, +): Promise { + const sourceText = await readFile(path, { encoding: "utf8" }); + return createTypeScriptSourceFile(sourceText); +} + +// TODO: merge typescript and ts-morph stuff diff --git a/yarn.lock b/yarn.lock index f7807d2f..adddaa8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1240,13 +1240,14 @@ __metadata: graphql: "npm:16.9.0" graphql-query-compress: "npm:1.2.4" graphql-request: "npm:7.1.0" + immutability-helper: "patch:immutability-helper@npm%3A3.1.1#~/.yarn/patches/immutability-helper-npm-3.1.1-482f1f8f58.patch" invariant: "npm:2.2.4" jest: "npm:29.7.0" nullthrows: "patch:nullthrows@npm%3A1.1.1#~/.yarn/patches/nullthrows-npm-1.1.1-3d1f817134.patch" prettier: "npm:3.3.3" ts-jest: "npm:29.2.5" + ts-to-zod: "npm:3.13.0" tsx: "npm:4.19.1" - type-fest: "npm:4.26.1" typescript: "npm:5.6.2" zod: "npm:3.23.8" languageName: unknown @@ -2850,6 +2851,32 @@ __metadata: languageName: node linkType: hard +"@oclif/core@npm:>=3.26.0": + version: 4.0.27 + resolution: "@oclif/core@npm:4.0.27" + dependencies: + ansi-escapes: "npm:^4.3.2" + ansis: "npm:^3.3.2" + clean-stack: "npm:^3.0.1" + cli-spinners: "npm:^2.9.2" + debug: "npm:^4.3.7" + ejs: "npm:^3.1.10" + get-package-type: "npm:^0.1.0" + globby: "npm:^11.1.0" + indent-string: "npm:^4.0.0" + is-wsl: "npm:^3" + lilconfig: "npm:^3.1.2" + minimatch: "npm:^9.0.5" + semver: "npm:^7.6.3" + string-width: "npm:^4.2.3" + supports-color: "npm:^8" + widest-line: "npm:^3.1.0" + wordwrap: "npm:^1.0.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/ee358d079785a23f88d816406b74eaa8bf00aba68b90307a5a46803bb96c7483ca3fd72f223cff1e2bfd0ca8a1fb1bdd4a76b165dad3e411a7dc19663cea960e + languageName: node + linkType: hard + "@peculiar/asn1-schema@npm:^2.3.8": version: 2.3.13 resolution: "@peculiar/asn1-schema@npm:2.3.13" @@ -3469,6 +3496,17 @@ __metadata: languageName: node linkType: hard +"@typescript/vfs@npm:^1.5.0": + version: 1.6.0 + resolution: "@typescript/vfs@npm:1.6.0" + dependencies: + debug: "npm:^4.1.1" + peerDependencies: + typescript: "*" + checksum: 10c0/35e17d92f0d4f33c4be12fc4468196788794bc2edc1a371f1023c42314f6d1e0e851f07b45732a634ef750e61e2ef8769e8ab4f6a6c511cea8da397fa87852ff + languageName: node + linkType: hard + "@vladfrangu/async_event_emitter@npm:^2.2.4, @vladfrangu/async_event_emitter@npm:^2.4.6": version: 2.4.6 resolution: "@vladfrangu/async_event_emitter@npm:2.4.6" @@ -3824,7 +3862,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -3888,6 +3926,13 @@ __metadata: languageName: node linkType: hard +"ansis@npm:^3.3.2": + version: 3.3.2 + resolution: "ansis@npm:3.3.2" + checksum: 10c0/7b5bd3d28e5fa12dbd2268e984b292540f863bb113fbaa78be93e512417df014e547279ecec264b39948364e13f245424a0bc911802d68def185545d2290d11c + languageName: node + linkType: hard + "anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -4338,7 +4383,7 @@ __metadata: languageName: node linkType: hard -"callsites@npm:^3.0.0": +"callsites@npm:^3.0.0, callsites@npm:^3.1.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 @@ -4387,6 +4432,13 @@ __metadata: languageName: node linkType: hard +"case@npm:^1.6.3": + version: 1.6.3 + resolution: "case@npm:1.6.3" + checksum: 10c0/43fcbb1dff1c4add94dd2bc98bd923d6616f10bff6959adf686d192c3db7d7ced35410761e1ac94cc4a1f5c41c86406ad79d390805539e421e8a77e553f67223 + languageName: node + linkType: hard + "chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -4538,7 +4590,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.5.3": +"chokidar@npm:^3.5.1, chokidar@npm:^3.5.3": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -4592,6 +4644,15 @@ __metadata: languageName: node linkType: hard +"clean-stack@npm:^3.0.1": + version: 3.0.1 + resolution: "clean-stack@npm:3.0.1" + dependencies: + escape-string-regexp: "npm:4.0.0" + checksum: 10c0/4ea5c03bdf78e8afb2592f34c1b5832d0c7858d37d8b0d40fba9d61a103508fa3bb527d39a99469019083e58e05d1ad54447e04217d5d36987e97182adab0e03 + languageName: node + linkType: hard + "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -4610,7 +4671,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 @@ -4950,7 +5011,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:~4.3.6": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.7, debug@npm:~4.3.6": version: 4.3.7 resolution: "debug@npm:4.3.7" dependencies: @@ -5491,6 +5552,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:4.0.0, escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -5505,13 +5573,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 - languageName: node - linkType: hard - "eslint-import-resolver-node@npm:^0.3.9": version: 0.3.9 resolution: "eslint-import-resolver-node@npm:0.3.9" @@ -5711,6 +5772,13 @@ __metadata: languageName: node linkType: hard +"esm@npm:^3.2.25": + version: 3.2.25 + resolution: "esm@npm:3.2.25" + checksum: 10c0/8e60e8075506a7ce28681c30c8f54623fe18a251c364cd481d86719fc77f58aa055b293d80632d9686d5408aaf865ffa434897dc9fd9153c8b3f469fad23f094 + languageName: node + linkType: hard + "espree@npm:^10.0.1, espree@npm:^10.2.0": version: 10.2.0 resolution: "espree@npm:10.2.0" @@ -6140,6 +6208,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.1.1": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -6378,7 +6457,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.0.3": +"globby@npm:^11.0.3, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -6782,7 +6861,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:^8.0.0": +"inquirer@npm:^8.0.0, inquirer@npm:^8.2.0": version: 8.2.6 resolution: "inquirer@npm:8.2.6" dependencies: @@ -6964,6 +7043,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -7017,6 +7105,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + "is-interactive@npm:^1.0.0": version: 1.0.0 resolution: "is-interactive@npm:1.0.0" @@ -7063,6 +7162,13 @@ __metadata: languageName: node linkType: hard +"is-observable@npm:^2.1.0": + version: 2.1.0 + resolution: "is-observable@npm:2.1.0" + checksum: 10c0/f6ae9e136f66ad59c4faa4661112c389b398461cdeb0ef5bc3c505989469b77b2ba4602e2abc54a635d65f616eec9b5a40cd7d2c1f96b2cc4748b56635eba1c6 + languageName: node + linkType: hard + "is-plain-obj@npm:^4.0.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -7189,6 +7295,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^3": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -7961,7 +8076,7 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:~3.1.2": +"lilconfig@npm:^3.1.2, lilconfig@npm:~3.1.2": version: 3.1.2 resolution: "lilconfig@npm:3.1.2" checksum: 10c0/f059630b1a9bddaeba83059db00c672b64dc14074e9f232adce32b38ca1b5686ab737eb665c5ba3c32f147f0002b4bee7311ad0386a9b98547b5623e87071fbe @@ -9011,6 +9126,13 @@ __metadata: languageName: node linkType: hard +"observable-fns@npm:^0.6.1": + version: 0.6.1 + resolution: "observable-fns@npm:0.6.1" + checksum: 10c0/bf25d5b3e4040233e886800c48b347361d9c7a1179f345590e671c2dd5ea9b4447bd5037f8ed40b2bb6fd7e205f0c5450eff15f48efdf91b3dec027007cf2834 + languageName: node + linkType: hard + "once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -9061,7 +9183,7 @@ __metadata: languageName: node linkType: hard -"ora@npm:^5.4.1": +"ora@npm:^5.4.0, ora@npm:^5.4.1": version: 5.4.1 resolution: "ora@npm:5.4.1" dependencies: @@ -9366,6 +9488,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:3.0.3": + version: 3.0.3 + resolution: "prettier@npm:3.0.3" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/f950887bc03c5b970d8c6dd129364acfbbc61e7b46aec5d5ce17f4adf6404e2ef43072c98b51c4786e0eaca949b307d362a773fd47502862d754b5a328fa2b26 + languageName: node + linkType: hard + "prettier@npm:3.2.5": version: 3.2.5 resolution: "prettier@npm:3.2.5" @@ -9837,7 +9968,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.5.5": +"rxjs@npm:^7.4.0, rxjs@npm:^7.5.5": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -10276,7 +10407,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -10423,7 +10554,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0": +"supports-color@npm:^8, supports-color@npm:^8.0.0": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -10523,6 +10654,22 @@ __metadata: languageName: node linkType: hard +"threads@npm:^1.7.0": + version: 1.7.0 + resolution: "threads@npm:1.7.0" + dependencies: + callsites: "npm:^3.1.0" + debug: "npm:^4.2.0" + is-observable: "npm:^2.1.0" + observable-fns: "npm:^0.6.1" + tiny-worker: "npm:>= 2" + dependenciesMeta: + tiny-worker: + optional: true + checksum: 10c0/34114075c5eb253e937c01e0e51babb71eb1571c2fd4a8170645aa20c10186422324537d017d64bfd3de655cbb6647bf70d1d602260273b1e9750719d67f359a + languageName: node + linkType: hard + "through@npm:^2.3.6, through@npm:^2.3.8": version: 2.3.8 resolution: "through@npm:2.3.8" @@ -10530,6 +10677,15 @@ __metadata: languageName: node linkType: hard +"tiny-worker@npm:>= 2": + version: 2.3.0 + resolution: "tiny-worker@npm:2.3.0" + dependencies: + esm: "npm:^3.2.25" + checksum: 10c0/3106cace86e673216426a517e96fb72ce642ba79002554e4c6bceb585ba77cf5e5e68b452c752cada6136ae94fdbf11c56943a70de6c6bc6a2a3a9ae439746c9 + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.9": version: 0.2.9 resolution: "tinyglobby@npm:0.2.9" @@ -10688,6 +10844,32 @@ __metadata: languageName: node linkType: hard +"ts-to-zod@npm:3.13.0": + version: 3.13.0 + resolution: "ts-to-zod@npm:3.13.0" + dependencies: + "@oclif/core": "npm:>=3.26.0" + "@typescript/vfs": "npm:^1.5.0" + case: "npm:^1.6.3" + chokidar: "npm:^3.5.1" + fs-extra: "npm:^11.1.1" + inquirer: "npm:^8.2.0" + lodash: "npm:^4.17.21" + ora: "npm:^5.4.0" + prettier: "npm:3.0.3" + rxjs: "npm:^7.4.0" + slash: "npm:^3.0.0" + threads: "npm:^1.7.0" + tslib: "npm:^2.3.1" + tsutils: "npm:^3.21.0" + typescript: "npm:^5.2.2" + zod: "npm:^3.23.8" + bin: + ts-to-zod: bin/run + checksum: 10c0/f385a8f2fd5f41bd8e4c8d68d8d8b949410b99181409427d22cec5eed763205bae46592da940c256fbd33877f1bebc9355280404e5e76e8e9cd3ff39f50a7803 + languageName: node + linkType: hard + "ts-toolbelt@npm:^9.6.0": version: 9.6.0 resolution: "ts-toolbelt@npm:9.6.0" @@ -10714,6 +10896,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^1.8.1": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + "tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2, tslib@npm:^2.6.3": version: 2.7.0 resolution: "tslib@npm:2.7.0" @@ -10735,6 +10924,17 @@ __metadata: languageName: node linkType: hard +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: "npm:^1.8.1" + peerDependencies: + typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 + languageName: node + linkType: hard + "tsx@npm:4.19.1": version: 4.19.1 resolution: "tsx@npm:4.19.1" @@ -10833,7 +11033,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.6.2": +"typescript@npm:5.6.2, typescript@npm:^5.2.2": version: 5.6.2 resolution: "typescript@npm:5.6.2" bin: @@ -10843,7 +11043,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.6.2#optional!builtin": +"typescript@patch:typescript@npm%3A5.6.2#optional!builtin, typescript@patch:typescript@npm%3A^5.2.2#optional!builtin": version: 5.6.2 resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin::version=5.6.2&hash=8c6c40" bin: @@ -11306,6 +11506,15 @@ __metadata: languageName: node linkType: hard +"widest-line@npm:^3.1.0": + version: 3.1.0 + resolution: "widest-line@npm:3.1.0" + dependencies: + string-width: "npm:^4.0.0" + checksum: 10c0/b1e623adcfb9df35350dd7fc61295d6d4a1eaa65a406ba39c4b8360045b614af95ad10e05abf704936ed022569be438c4bfa02d6d031863c4166a238c301119f + languageName: node + linkType: hard + "wildcard@npm:^2.0.0": version: 2.0.1 resolution: "wildcard@npm:2.0.1" @@ -11320,6 +11529,13 @@ __metadata: languageName: node linkType: hard +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 10c0/7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 + languageName: node + linkType: hard + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0"