diff --git a/CHANGELOG.md b/CHANGELOG.md index 1229d0f9..f7587a42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ ## Version 2 +### v2.2.0 + +- Naming of circular types is now numeric: + - Deprecated `serializer` property on the `Integration` constructor argument (no longer used). + +```diff +- type Type2048581c137c5b2130eb860e3ae37da196dfc25b = { ++ type Type1 = { + title: string; +- features: Type2048581c137c5b2130eb860e3ae37da196dfc25b; ++ features: Type1; + }[]; +``` + ### v2.1.1 - Documentation update on compatibility with Express Zod API v21; diff --git a/src/__snapshots__/integration.spec.ts.snap b/src/__snapshots__/integration.spec.ts.snap index 27be0160..e467f072 100644 --- a/src/__snapshots__/integration.spec.ts.snap +++ b/src/__snapshots__/integration.spec.ts.snap @@ -6,9 +6,9 @@ exports[`Integration > print() > should handle circular references 1`] = ` export namespace Root { /** @desc The actual path of the Root namespace */ export const path = "/"; - type Type2048581c137c5b2130eb860e3ae37da196dfc25b = { + type Type1 = { title: string; - features: Type2048581c137c5b2130eb860e3ae37da196dfc25b; + features: Type1; }[]; export interface Emission { time: (currentIsoTime: string) => void; @@ -22,7 +22,7 @@ export namespace Root { export interface Actions { test: (p1: { title: string; - features: Type2048581c137c5b2130eb860e3ae37da196dfc25b; + features: Type1; }) => void; } /** @example const socket: Root.Socket = io(Root.path) */ diff --git a/src/__snapshots__/zts.spec.ts.snap b/src/__snapshots__/zts.spec.ts.snap index e331d7d7..4db6bfe8 100644 --- a/src/__snapshots__/zts.spec.ts.snap +++ b/src/__snapshots__/zts.spec.ts.snap @@ -8,7 +8,7 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = ` string: string; }[]; boolean: boolean; - circular: Type118cb3b11b8a1f3b6b1e60a89f96a8be9da32a0f; + circular: SomeType; union: { number: number; } | "hi"; @@ -60,7 +60,7 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = ` optDefaultString?: string | undefined; refinedStringWithSomeBullshit: (string | number) & ((bigint | null) | undefined); nativeEnum: "A" | "apple" | "banana" | "cantaloupe" | 5; - lazy: Type51497f7e879bae48c3fbad2fa68050d1e08bbf82; + lazy: SomeType; discUnion: { kind: "circle"; radius: number; diff --git a/src/integration-helpers.ts b/src/integration-helpers.ts index e508f4a0..eb526b66 100644 --- a/src/integration-helpers.ts +++ b/src/integration-helpers.ts @@ -1,4 +1,3 @@ -import { createHash } from "node:crypto"; import { range } from "ramda"; import ts from "typescript"; import { z } from "zod"; @@ -6,9 +5,6 @@ import { z } from "zod"; export const f = ts.factory; export const exportModifier = [f.createModifier(ts.SyntaxKind.ExportKeyword)]; -export const defaultSerializer = (schema: z.ZodTypeAny): string => - createHash("sha1").update(JSON.stringify(schema), "utf8").digest("hex"); - export const makeEventFnSchema = ( base: z.AnyZodTuple, ack?: z.AnyZodTuple, diff --git a/src/integration.ts b/src/integration.ts index 60e39c32..d444bdf3 100644 --- a/src/integration.ts +++ b/src/integration.ts @@ -3,12 +3,7 @@ import { z } from "zod"; import { AbstractAction } from "./action"; import { makeCleanId } from "./common-helpers"; import { Config } from "./config"; -import { - defaultSerializer, - exportModifier, - f, - makeEventFnSchema, -} from "./integration-helpers"; +import { exportModifier, f, makeEventFnSchema } from "./integration-helpers"; import { Namespaces, normalizeNS } from "./namespace"; import { zodToTs } from "./zts"; import { addJsDocComment, createTypeAlias, printNode } from "./zts-helpers"; @@ -24,8 +19,8 @@ interface IntegrationProps { */ maxOverloads?: number; /** - * @desc Used for comparing schemas wrapped into z.lazy() to limit the recursion - * @default JSON.stringify() + SHA1 hash as a hex digest + * @deprecated unused + * @todo remove in next major * */ serializer?: (schema: z.ZodTypeAny) => string; /** @@ -53,7 +48,7 @@ export class Integration { protected program: ts.Node[] = []; protected aliases: Record< string, // namespace - Record + Map > = {}; protected ids = { path: f.createIdentifier("path"), @@ -71,28 +66,24 @@ export class Integration { > > = {}; - protected getAlias( - ns: string, - name: string, - ): ts.TypeReferenceNode | undefined { - return name in this.aliases[ns] - ? f.createTypeReferenceNode(name) - : undefined; - } - protected makeAlias( ns: string, - name: string, - type: ts.TypeNode, + schema: z.ZodTypeAny, + produce: () => ts.TypeNode, ): ts.TypeReferenceNode { - this.aliases[ns][name] = createTypeAlias(type, name); - return this.getAlias(ns, name)!; + let name = this.aliases[ns].get(schema)?.name?.text; + if (!name) { + name = `Type${this.aliases[ns].size + 1}`; + const temp = f.createLiteralTypeNode(f.createNull()); + this.aliases[ns].set(schema, createTypeAlias(temp, name)); + this.aliases[ns].set(schema, createTypeAlias(produce(), name)); + } + return f.createTypeReferenceNode(name); } constructor({ config: { namespaces }, actions, - serializer = defaultSerializer, optionalPropStyle = { withQuestionMark: true, withUndefined: true }, maxOverloads = 3, }: IntegrationProps) { @@ -115,12 +106,10 @@ export class Integration { ); for (const [ns, { emission }] of Object.entries(namespaces)) { - this.aliases[ns] = {}; + this.aliases[ns] = new Map(); this.registry[ns] = { emission: [], actions: [] }; const commons = { - getAlias: this.getAlias.bind(this, ns), makeAlias: this.makeAlias.bind(this, ns), - serializer, optionalPropStyle, }; for (const [event, { schema, ack }] of Object.entries(emission)) { @@ -197,7 +186,7 @@ export class Integration { f.createIdentifier(publicName), f.createModuleBlock([ nsNameNode, - ...Object.values(this.aliases[ns]), + ...this.aliases[ns].values(), ...interfaces, socketNode, ]), diff --git a/src/zts-helpers.ts b/src/zts-helpers.ts index 8a674b27..51dc999a 100644 --- a/src/zts-helpers.ts +++ b/src/zts-helpers.ts @@ -9,9 +9,10 @@ export type LiteralType = string | number | boolean; export interface ZTSContext extends FlatObject { direction: "in" | "out"; - getAlias: (name: string) => ts.TypeReferenceNode | undefined; - makeAlias: (name: string, type: ts.TypeNode) => ts.TypeReferenceNode; - serializer: (schema: z.ZodTypeAny) => string; + makeAlias: ( + schema: z.ZodTypeAny, + produce: () => ts.TypeNode, + ) => ts.TypeReferenceNode; optionalPropStyle: { withQuestionMark?: boolean; withUndefined?: boolean }; } @@ -28,18 +29,16 @@ export const addJsDocComment = (node: ts.Node, text: string) => { export const createTypeAlias = ( node: ts.TypeNode, - identifier: string, + name: string, comment?: string, ) => { const typeAlias = f.createTypeAliasDeclaration( undefined, - f.createIdentifier(identifier), + f.createIdentifier(name), undefined, node, ); - if (comment) { - addJsDocComment(typeAlias, comment); - } + if (comment) addJsDocComment(typeAlias, comment); return typeAlias; }; diff --git a/src/zts.spec.ts b/src/zts.spec.ts index 77bf6211..ae6a1dd9 100644 --- a/src/zts.spec.ts +++ b/src/zts.spec.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import ts from "typescript"; import { z } from "zod"; -import { defaultSerializer, f } from "./integration-helpers"; +import { f } from "./integration-helpers"; import { zodToTs } from "./zts"; import { ZTSContext, createTypeAlias, printNode } from "./zts-helpers"; import { describe, expect, test, vi } from "vitest"; @@ -11,9 +11,7 @@ describe("zod-to-ts", () => { printNode(node, { newLine: ts.NewLineKind.LineFeed }); const defaultCtx: ZTSContext = { direction: "in", - getAlias: vi.fn((name: string) => f.createTypeReferenceNode(name)), - makeAlias: vi.fn(), - serializer: defaultSerializer, + makeAlias: vi.fn(() => f.createTypeReferenceNode("SomeType")), optionalPropStyle: { withQuestionMark: true, withUndefined: true }, }; diff --git a/src/zts.ts b/src/zts.ts index e7859f59..838ee254 100644 --- a/src/zts.ts +++ b/src/zts.ts @@ -196,19 +196,8 @@ const onNull: Producer = () => f.createLiteralTypeNode(f.createNull()); const onDate: Producer = () => f.createTypeReferenceNode(f.createIdentifier("Date")); -const onLazy: Producer = ( - lazy: z.ZodLazy, - { getAlias, makeAlias, next, serializer: serialize }, -) => { - const name = `Type${serialize(lazy.schema)}`; - return ( - getAlias(name) || - (() => { - makeAlias(name, f.createLiteralTypeNode(f.createNull())); // make empty type first - return makeAlias(name, next(lazy.schema)); // update - })() - ); -}; +const onLazy: Producer = (lazy: z.ZodLazy, { makeAlias, next }) => + makeAlias(lazy, () => next(lazy.schema)); const onFunction: Producer = ( schema: z.ZodFunction,