zod-compat.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // zod-compat.ts
  2. // ----------------------------------------------------
  3. // Unified types + helpers to accept Zod v3 and v4 (Mini)
  4. // ----------------------------------------------------
  5. import * as z3rt from 'zod/v3';
  6. import * as z4mini from 'zod/v4-mini';
  7. // --- Runtime detection ---
  8. export function isZ4Schema(s) {
  9. // Present on Zod 4 (Classic & Mini) schemas; absent on Zod 3
  10. const schema = s;
  11. return !!schema._zod;
  12. }
  13. // --- Schema construction ---
  14. export function objectFromShape(shape) {
  15. const values = Object.values(shape);
  16. if (values.length === 0)
  17. return z4mini.object({}); // default to v4 Mini
  18. const allV4 = values.every(isZ4Schema);
  19. const allV3 = values.every(s => !isZ4Schema(s));
  20. if (allV4)
  21. return z4mini.object(shape);
  22. if (allV3)
  23. return z3rt.object(shape);
  24. throw new Error('Mixed Zod versions detected in object shape.');
  25. }
  26. // --- Unified parsing ---
  27. export function safeParse(schema, data) {
  28. if (isZ4Schema(schema)) {
  29. // Mini exposes top-level safeParse
  30. const result = z4mini.safeParse(schema, data);
  31. return result;
  32. }
  33. const v3Schema = schema;
  34. const result = v3Schema.safeParse(data);
  35. return result;
  36. }
  37. export async function safeParseAsync(schema, data) {
  38. if (isZ4Schema(schema)) {
  39. // Mini exposes top-level safeParseAsync
  40. const result = await z4mini.safeParseAsync(schema, data);
  41. return result;
  42. }
  43. const v3Schema = schema;
  44. const result = await v3Schema.safeParseAsync(data);
  45. return result;
  46. }
  47. // --- Shape extraction ---
  48. export function getObjectShape(schema) {
  49. if (!schema)
  50. return undefined;
  51. // Zod v3 exposes `.shape`; Zod v4 keeps the shape on `_zod.def.shape`
  52. let rawShape;
  53. if (isZ4Schema(schema)) {
  54. const v4Schema = schema;
  55. rawShape = v4Schema._zod?.def?.shape;
  56. }
  57. else {
  58. const v3Schema = schema;
  59. rawShape = v3Schema.shape;
  60. }
  61. if (!rawShape)
  62. return undefined;
  63. if (typeof rawShape === 'function') {
  64. try {
  65. return rawShape();
  66. }
  67. catch {
  68. return undefined;
  69. }
  70. }
  71. return rawShape;
  72. }
  73. // --- Schema normalization ---
  74. /**
  75. * Normalizes a schema to an object schema. Handles both:
  76. * - Already-constructed object schemas (v3 or v4)
  77. * - Raw shapes that need to be wrapped into object schemas
  78. */
  79. export function normalizeObjectSchema(schema) {
  80. if (!schema)
  81. return undefined;
  82. // First check if it's a raw shape (Record<string, AnySchema>)
  83. // Raw shapes don't have _def or _zod properties and aren't schemas themselves
  84. if (typeof schema === 'object') {
  85. // Check if it's actually a ZodRawShapeCompat (not a schema instance)
  86. // by checking if it lacks schema-like internal properties
  87. const asV3 = schema;
  88. const asV4 = schema;
  89. // If it's not a schema instance (no _def or _zod), it might be a raw shape
  90. if (!asV3._def && !asV4._zod) {
  91. // Check if all values are schemas (heuristic to confirm it's a raw shape)
  92. const values = Object.values(schema);
  93. if (values.length > 0 &&
  94. values.every(v => typeof v === 'object' &&
  95. v !== null &&
  96. (v._def !== undefined ||
  97. v._zod !== undefined ||
  98. typeof v.parse === 'function'))) {
  99. return objectFromShape(schema);
  100. }
  101. }
  102. }
  103. // If we get here, it should be an AnySchema (not a raw shape)
  104. // Check if it's already an object schema
  105. if (isZ4Schema(schema)) {
  106. // Check if it's a v4 object
  107. const v4Schema = schema;
  108. const def = v4Schema._zod?.def;
  109. if (def && (def.type === 'object' || def.shape !== undefined)) {
  110. return schema;
  111. }
  112. }
  113. else {
  114. // Check if it's a v3 object
  115. const v3Schema = schema;
  116. if (v3Schema.shape !== undefined) {
  117. return schema;
  118. }
  119. }
  120. return undefined;
  121. }
  122. // --- Error message extraction ---
  123. /**
  124. * Safely extracts an error message from a parse result error.
  125. * Zod errors can have different structures, so we handle various cases.
  126. */
  127. export function getParseErrorMessage(error) {
  128. if (error && typeof error === 'object') {
  129. // Try common error structures
  130. if ('message' in error && typeof error.message === 'string') {
  131. return error.message;
  132. }
  133. if ('issues' in error && Array.isArray(error.issues) && error.issues.length > 0) {
  134. const firstIssue = error.issues[0];
  135. if (firstIssue && typeof firstIssue === 'object' && 'message' in firstIssue) {
  136. return String(firstIssue.message);
  137. }
  138. }
  139. // Fallback: try to stringify the error
  140. try {
  141. return JSON.stringify(error);
  142. }
  143. catch {
  144. return String(error);
  145. }
  146. }
  147. return String(error);
  148. }
  149. // --- Schema metadata access ---
  150. /**
  151. * Gets the description from a schema, if available.
  152. * Works with both Zod v3 and v4.
  153. *
  154. * Both versions expose a `.description` getter that returns the description
  155. * from their respective internal storage (v3: _def, v4: globalRegistry).
  156. */
  157. export function getSchemaDescription(schema) {
  158. return schema.description;
  159. }
  160. /**
  161. * Checks if a schema is optional.
  162. * Works with both Zod v3 and v4.
  163. */
  164. export function isSchemaOptional(schema) {
  165. if (isZ4Schema(schema)) {
  166. const v4Schema = schema;
  167. return v4Schema._zod?.def?.type === 'optional';
  168. }
  169. const v3Schema = schema;
  170. // v3 has isOptional() method
  171. if (typeof schema.isOptional === 'function') {
  172. return schema.isOptional();
  173. }
  174. return v3Schema._def?.typeName === 'ZodOptional';
  175. }
  176. /**
  177. * Gets the literal value from a schema, if it's a literal schema.
  178. * Works with both Zod v3 and v4.
  179. * Returns undefined if the schema is not a literal or the value cannot be determined.
  180. */
  181. export function getLiteralValue(schema) {
  182. if (isZ4Schema(schema)) {
  183. const v4Schema = schema;
  184. const def = v4Schema._zod?.def;
  185. if (def) {
  186. // Try various ways to get the literal value
  187. if (def.value !== undefined)
  188. return def.value;
  189. if (Array.isArray(def.values) && def.values.length > 0) {
  190. return def.values[0];
  191. }
  192. }
  193. }
  194. const v3Schema = schema;
  195. const def = v3Schema._def;
  196. if (def) {
  197. if (def.value !== undefined)
  198. return def.value;
  199. if (Array.isArray(def.values) && def.values.length > 0) {
  200. return def.values[0];
  201. }
  202. }
  203. // Fallback: check for direct value property (some Zod versions)
  204. const directValue = schema.value;
  205. if (directValue !== undefined)
  206. return directValue;
  207. return undefined;
  208. }
  209. //# sourceMappingURL=zod-compat.js.map