{"version":3,"file":"zod-to-json.cjs","names":[],"sources":["../../src/zod-to-json.ts"],"sourcesContent":["import type {\n  $ZodDate,\n  $ZodUndefined,\n  $ZodUnion,\n  JSONSchema,\n  RegistryToJSONSchemaParams,\n} from 'zod/v4/core'\nimport { $ZodRegistry, $ZodType, toJSONSchema } from 'zod/v4/core'\nimport type { SchemaRegistryMeta } from './registry'\nimport { getReferenceUri } from './utils'\n\nconst SCHEMA_REGISTRY_ID_PLACEHOLDER = '__SCHEMA__ID__PLACEHOLDER__'\nconst SCHEMA_URI_PLACEHOLDER = '__SCHEMA__PLACEHOLDER__'\n\n/**\n * Identity keywords that zod stamps on the root of a generated schema. They are not\n * meaningful in the emitted document (we reference schemas through `$ref`), so they are\n * removed for every target.\n */\nconst IDENTITY_KEYWORDS = ['id', '$id', '$schema']\n\n/**\n * Keywords that zod's `openapi-3.0` target can still emit (e.g. `contentEncoding` for\n * `z.base64()`) but that are not part of the OpenAPI 3.0 schema subset. OpenAPI 3.1 /\n * JSON Schema 2020-12 (`draft-2020-12`) does allow them, so they are only removed for 3.0.\n */\nconst OAS_3_0_INVALID_KEYWORDS = [\n  'unevaluatedProperties',\n  'dependentSchemas',\n  'patternProperties',\n  'propertyNames',\n  'contentEncoding',\n  'contentMediaType',\n]\n\nconst getRemoveKeys = (target: RegistryToJSONSchemaParams['target']): Set<string> => {\n  return new Set(\n    target === 'openapi-3.0'\n      ? [...IDENTITY_KEYWORDS, ...OAS_3_0_INVALID_KEYWORDS]\n      : IDENTITY_KEYWORDS,\n  )\n}\n\n// Keywords whose value is a single subschema.\nconst SINGLE_SCHEMA_KEYWORDS = ['items', 'additionalProperties', 'not']\n// Keywords whose value is an array of subschemas.\nconst SCHEMA_LIST_KEYWORDS = ['allOf', 'anyOf', 'oneOf']\n\n/**\n * Removes `removeKeys` from a generated JSON schema, recursing only into positions that\n * actually hold subschemas (`properties` values, `items`, `additionalProperties`,\n * `allOf`/`anyOf`/`oneOf`, `not`). It deliberately does not walk by key name everywhere:\n * a user property called `id` lives as a key inside `properties` and must be preserved,\n * while the `id` keyword on a schema object must be dropped.\n */\nconst sanitizeSchema = (value: unknown, removeKeys: Set<string>): any => {\n  if (Array.isArray(value)) {\n    return value.map((entry) => sanitizeSchema(entry, removeKeys))\n  }\n  if (value === null || typeof value !== 'object') {\n    return value\n  }\n\n  const result: Record<string, any> = {}\n  for (const [key, child] of Object.entries(value)) {\n    if (removeKeys.has(key)) {\n      continue\n    }\n\n    if (key === 'properties' && child !== null && typeof child === 'object') {\n      result[key] = Object.fromEntries(\n        Object.entries(child).map(([name, schema]) => [name, sanitizeSchema(schema, removeKeys)]),\n      )\n    } else if (SINGLE_SCHEMA_KEYWORDS.includes(key)) {\n      result[key] =\n        child !== null && typeof child === 'object' ? sanitizeSchema(child, removeKeys) : child\n    } else if (SCHEMA_LIST_KEYWORDS.includes(key)) {\n      result[key] = Array.isArray(child)\n        ? child.map((entry) => sanitizeSchema(entry, removeKeys))\n        : child\n    } else {\n      result[key] = child\n    }\n  }\n\n  return result\n}\n\nfunction isZodDate(entity: unknown): entity is $ZodDate {\n  return entity instanceof $ZodType && entity._zod.def.type === 'date'\n}\n\nfunction isZodUnion(entity: unknown): entity is $ZodUnion {\n  return entity instanceof $ZodType && entity._zod.def.type === 'union'\n}\n\nfunction isZodUndefined(entity: unknown): entity is $ZodUndefined {\n  return entity instanceof $ZodType && entity._zod.def.type === 'undefined'\n}\n\ntype NullableSchema = JSONSchema.BaseSchema & { nullable?: boolean }\n\n/**\n * The `openapi-3.0` target has no `null` type, so zod emits `z.null()` as\n * `{ type: 'string', nullable: true, enum: [null] }`. Detect that branch so a nullable\n * union can be collapsed back into a single `nullable: true` schema.\n */\nconst isNullBranch = (schema: NullableSchema): boolean =>\n  schema.nullable === true &&\n  Array.isArray(schema.enum) &&\n  schema.enum.length === 1 &&\n  schema.enum[0] === null\n\nconst getOverride = (\n  ctx: {\n    zodSchema: $ZodType\n    jsonSchema: JSONSchema.BaseSchema\n  },\n  io: 'input' | 'output',\n  target: RegistryToJSONSchemaParams['target'],\n) => {\n  if (isZodUnion(ctx.zodSchema)) {\n    // Filter unrepresentable types in unions\n    // TODO: Should be fixed upstream and not merged in this plugin.\n    // Remove when passed: https://github.com/colinhacks/zod/pull/5013\n    ctx.jsonSchema.anyOf = ctx.jsonSchema.anyOf?.filter((schema) => Object.keys(schema).length > 0)\n\n    // OpenAPI 3.0 has no `null` type, so collapse the null branch of a nullable union into\n    // `nullable: true` rather than leaving a `{ enum: [null] }` member in the `anyOf`.\n    // @see https://github.com/turkerdev/fastify-type-provider-zod/issues/192\n    const anyOf = ctx.jsonSchema.anyOf as NullableSchema[] | undefined\n    if (target === 'openapi-3.0' && anyOf) {\n      const otherBranches = anyOf.filter((schema) => !isNullBranch(schema))\n      if (otherBranches.length < anyOf.length && otherBranches.length > 0) {\n        if (otherBranches.length === 1) {\n          // Only one non-null member remains: merge it up and drop the `anyOf` wrapper.\n          delete ctx.jsonSchema.anyOf\n          Object.assign(ctx.jsonSchema, otherBranches[0], { nullable: true })\n        } else {\n          // Keep the remaining members and mark the whole schema nullable.\n          ctx.jsonSchema.anyOf = otherBranches\n          ;(ctx.jsonSchema as NullableSchema).nullable = true\n        }\n      }\n    }\n  }\n\n  if (isZodDate(ctx.zodSchema)) {\n    // Allow dates to be represented as strings in output schemas\n    if (io === 'output') {\n      ctx.jsonSchema.type = 'string'\n      ctx.jsonSchema.format = 'date-time'\n    }\n  }\n\n  if (isZodUndefined(ctx.zodSchema)) {\n    // Allow undefined to be represented as null in output schemas\n    if (io === 'output') {\n      ctx.jsonSchema.type = 'null'\n    }\n  }\n}\n\nexport type ZodToJsonConfig = Omit<\n  RegistryToJSONSchemaParams,\n  'io' | 'metadata' | 'cycles' | 'reused' | 'uri'\n>\n\nexport const zodSchemaToJson: (\n  zodSchema: $ZodType,\n  registry: $ZodRegistry<SchemaRegistryMeta>,\n  io: 'input' | 'output',\n  config: ZodToJsonConfig,\n) => JSONSchema.BaseSchema = (zodSchema, registry, io, config) => {\n  /**\n   * Checks whether the provided schema is registered in the given registry.\n   * If it is present and has an `id`, it can be referenced as component.\n   *\n   * @see https://github.com/turkerdev/fastify-type-provider-zod/issues/173\n   */\n  const schemaRegistryEntry = registry.get(zodSchema)\n  if (schemaRegistryEntry?.id) {\n    return { $ref: getReferenceUri(schemaRegistryEntry.id) }\n  }\n\n  /**\n   * Unfortunately, at the time of writing, there is no way to generate a schema with `$ref`\n   * using `toJSONSchema` and a zod schema.\n   *\n   * As a workaround, we create a zod registry containing only the specific schema we want to convert.\n   *\n   * @see https://github.com/colinhacks/zod/issues/4281\n   */\n  const tempRegistry = new $ZodRegistry<SchemaRegistryMeta>()\n  tempRegistry.add(zodSchema, { id: SCHEMA_REGISTRY_ID_PLACEHOLDER })\n\n  const {\n    schemas: { [SCHEMA_REGISTRY_ID_PLACEHOLDER]: result },\n  } = toJSONSchema(tempRegistry, {\n    ...config,\n    io,\n    metadata: registry,\n    unrepresentable: config.unrepresentable ?? 'any',\n    cycles: 'ref',\n    reused: 'inline',\n    /**\n     * The uri option only allows customizing the base path of the `$ref`, and it automatically appends a path to it.\n     * As a workaround, we set a placeholder that looks something like this.\n     * @see https://github.com/colinhacks/zod/issues/4750\n     */\n    uri: () => SCHEMA_URI_PLACEHOLDER,\n    override: config.override ?? ((ctx) => getOverride(ctx, io, config.target)),\n  })\n\n  /**\n   * Remove identity/target-incompatible keywords first. This also drops the root `$id`\n   * that zod sets to the uri placeholder, so the ref replacement below only ever sees the\n   * placeholders that stand for real component references.\n   */\n  const sanitized = sanitizeSchema(result, getRemoveKeys(config.target))\n\n  /**\n   * Replace the placeholder `$ref` values with the final component reference.\n   */\n  return JSON.parse(JSON.stringify(sanitized), (_key, value) =>\n    typeof value === 'string' && value.startsWith(SCHEMA_URI_PLACEHOLDER)\n      ? getReferenceUri(value.slice(SCHEMA_URI_PLACEHOLDER.length))\n      : value,\n  ) as JSONSchema.BaseSchema\n}\n\nexport const zodRegistryToJson: (\n  registry: $ZodRegistry<SchemaRegistryMeta>,\n  io: 'input' | 'output',\n  config: ZodToJsonConfig,\n) => Record<string, JSONSchema.BaseSchema> = (registry, io, config) => {\n  const result = toJSONSchema(registry, {\n    ...config,\n    io,\n    metadata: registry,\n    unrepresentable: config.unrepresentable ?? 'any',\n    cycles: 'ref',\n    reused: 'inline',\n    uri: (id) => getReferenceUri(id),\n    override: config.override ?? ((ctx) => getOverride(ctx, io, config.target)),\n  }).schemas\n\n  const removeKeys = getRemoveKeys(config.target)\n\n  const jsonSchemas: Record<string, JSONSchema.BaseSchema> = {}\n  for (const id in result) {\n    jsonSchemas[id] = sanitizeSchema(result[id], removeKeys)\n  }\n\n  return jsonSchemas\n}\n"],"mappings":"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"}