bupkis 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +35 -11
  3. package/dist/commonjs/assertion/assertion-async.d.ts +2 -1
  4. package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
  5. package/dist/commonjs/assertion/assertion-async.js +84 -2
  6. package/dist/commonjs/assertion/assertion-async.js.map +1 -1
  7. package/dist/commonjs/assertion/assertion-sync.d.ts +1 -1
  8. package/dist/commonjs/assertion/assertion-sync.d.ts.map +1 -1
  9. package/dist/commonjs/assertion/assertion-sync.js +5 -1
  10. package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
  11. package/dist/commonjs/assertion/assertion-types.d.ts +6 -2
  12. package/dist/commonjs/assertion/assertion-types.d.ts.map +1 -1
  13. package/dist/commonjs/assertion/assertion.d.ts +1 -1
  14. package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
  15. package/dist/commonjs/assertion/assertion.js +1 -14
  16. package/dist/commonjs/assertion/assertion.js.map +1 -1
  17. package/dist/commonjs/assertion/impl/async.d.ts +122 -21
  18. package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
  19. package/dist/commonjs/assertion/impl/async.js +118 -90
  20. package/dist/commonjs/assertion/impl/async.js.map +1 -1
  21. package/dist/commonjs/assertion/impl/callback.d.ts +104 -0
  22. package/dist/commonjs/assertion/impl/callback.d.ts.map +1 -0
  23. package/dist/commonjs/assertion/impl/callback.js +694 -0
  24. package/dist/commonjs/assertion/impl/callback.js.map +1 -0
  25. package/dist/commonjs/assertion/impl/index.d.ts +1 -1
  26. package/dist/commonjs/assertion/impl/index.d.ts.map +1 -1
  27. package/dist/commonjs/assertion/impl/index.js.map +1 -1
  28. package/dist/commonjs/assertion/impl/sync-basic.d.ts.map +1 -1
  29. package/dist/commonjs/assertion/impl/sync-basic.js +1 -1
  30. package/dist/commonjs/assertion/impl/sync-basic.js.map +1 -1
  31. package/dist/commonjs/assertion/impl/sync-collection.d.ts +1 -1
  32. package/dist/commonjs/assertion/impl/sync-collection.js +3 -3
  33. package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
  34. package/dist/commonjs/assertion/impl/sync-esoteric.js +1 -1
  35. package/dist/commonjs/assertion/impl/sync-esoteric.js.map +1 -1
  36. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +22 -28
  37. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  38. package/dist/commonjs/assertion/impl/sync-parametric.js +35 -50
  39. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  40. package/dist/commonjs/assertion/impl/sync.d.ts +68 -30
  41. package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
  42. package/dist/commonjs/assertion/impl/sync.js +4 -1
  43. package/dist/commonjs/assertion/impl/sync.js.map +1 -1
  44. package/dist/commonjs/bootstrap.d.ts +147 -52
  45. package/dist/commonjs/bootstrap.d.ts.map +1 -1
  46. package/dist/commonjs/bootstrap.js +2 -3
  47. package/dist/commonjs/bootstrap.js.map +1 -1
  48. package/dist/commonjs/constant.d.ts +1 -1
  49. package/dist/commonjs/constant.d.ts.map +1 -1
  50. package/dist/commonjs/constant.js +8 -1
  51. package/dist/commonjs/constant.js.map +1 -1
  52. package/dist/commonjs/error.d.ts +22 -2
  53. package/dist/commonjs/error.d.ts.map +1 -1
  54. package/dist/commonjs/error.js +44 -4
  55. package/dist/commonjs/error.js.map +1 -1
  56. package/dist/commonjs/expect.d.ts.map +1 -1
  57. package/dist/commonjs/expect.js +1 -1
  58. package/dist/commonjs/expect.js.map +1 -1
  59. package/dist/commonjs/guards.d.ts +96 -5
  60. package/dist/commonjs/guards.d.ts.map +1 -1
  61. package/dist/commonjs/guards.js +104 -25
  62. package/dist/commonjs/guards.js.map +1 -1
  63. package/dist/commonjs/index.d.ts +146 -51
  64. package/dist/commonjs/index.d.ts.map +1 -1
  65. package/dist/commonjs/index.js.map +1 -1
  66. package/dist/commonjs/schema.d.ts +84 -18
  67. package/dist/commonjs/schema.d.ts.map +1 -1
  68. package/dist/commonjs/schema.js +107 -22
  69. package/dist/commonjs/schema.js.map +1 -1
  70. package/dist/commonjs/types.d.ts +171 -9
  71. package/dist/commonjs/types.d.ts.map +1 -1
  72. package/dist/commonjs/use.d.ts.map +1 -1
  73. package/dist/commonjs/use.js +15 -1
  74. package/dist/commonjs/use.js.map +1 -1
  75. package/dist/commonjs/util.d.ts +66 -50
  76. package/dist/commonjs/util.d.ts.map +1 -1
  77. package/dist/commonjs/util.js +169 -156
  78. package/dist/commonjs/util.js.map +1 -1
  79. package/dist/commonjs/value-to-schema.d.ts +122 -0
  80. package/dist/commonjs/value-to-schema.d.ts.map +1 -0
  81. package/dist/commonjs/value-to-schema.js +329 -0
  82. package/dist/commonjs/value-to-schema.js.map +1 -0
  83. package/dist/esm/assertion/assertion-async.d.ts +2 -1
  84. package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
  85. package/dist/esm/assertion/assertion-async.js +85 -3
  86. package/dist/esm/assertion/assertion-async.js.map +1 -1
  87. package/dist/esm/assertion/assertion-sync.d.ts +1 -1
  88. package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
  89. package/dist/esm/assertion/assertion-sync.js +6 -2
  90. package/dist/esm/assertion/assertion-sync.js.map +1 -1
  91. package/dist/esm/assertion/assertion-types.d.ts +6 -2
  92. package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
  93. package/dist/esm/assertion/assertion.d.ts +1 -1
  94. package/dist/esm/assertion/assertion.d.ts.map +1 -1
  95. package/dist/esm/assertion/assertion.js +1 -14
  96. package/dist/esm/assertion/assertion.js.map +1 -1
  97. package/dist/esm/assertion/impl/async.d.ts +122 -21
  98. package/dist/esm/assertion/impl/async.d.ts.map +1 -1
  99. package/dist/esm/assertion/impl/async.js +118 -90
  100. package/dist/esm/assertion/impl/async.js.map +1 -1
  101. package/dist/esm/assertion/impl/callback.d.ts +104 -0
  102. package/dist/esm/assertion/impl/callback.d.ts.map +1 -0
  103. package/dist/esm/assertion/impl/callback.js +691 -0
  104. package/dist/esm/assertion/impl/callback.js.map +1 -0
  105. package/dist/esm/assertion/impl/index.d.ts +1 -1
  106. package/dist/esm/assertion/impl/index.d.ts.map +1 -1
  107. package/dist/esm/assertion/impl/index.js +1 -1
  108. package/dist/esm/assertion/impl/index.js.map +1 -1
  109. package/dist/esm/assertion/impl/sync-basic.d.ts.map +1 -1
  110. package/dist/esm/assertion/impl/sync-basic.js +2 -2
  111. package/dist/esm/assertion/impl/sync-basic.js.map +1 -1
  112. package/dist/esm/assertion/impl/sync-collection.d.ts +1 -1
  113. package/dist/esm/assertion/impl/sync-collection.js +3 -3
  114. package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
  115. package/dist/esm/assertion/impl/sync-esoteric.js +2 -2
  116. package/dist/esm/assertion/impl/sync-esoteric.js.map +1 -1
  117. package/dist/esm/assertion/impl/sync-parametric.d.ts +22 -28
  118. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  119. package/dist/esm/assertion/impl/sync-parametric.js +36 -51
  120. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  121. package/dist/esm/assertion/impl/sync.d.ts +68 -30
  122. package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
  123. package/dist/esm/assertion/impl/sync.js +3 -1
  124. package/dist/esm/assertion/impl/sync.js.map +1 -1
  125. package/dist/esm/bootstrap.d.ts +147 -52
  126. package/dist/esm/bootstrap.d.ts.map +1 -1
  127. package/dist/esm/bootstrap.js +1 -2
  128. package/dist/esm/bootstrap.js.map +1 -1
  129. package/dist/esm/constant.d.ts +1 -1
  130. package/dist/esm/constant.d.ts.map +1 -1
  131. package/dist/esm/constant.js +7 -0
  132. package/dist/esm/constant.js.map +1 -1
  133. package/dist/esm/error.d.ts +22 -2
  134. package/dist/esm/error.d.ts.map +1 -1
  135. package/dist/esm/error.js +43 -4
  136. package/dist/esm/error.js.map +1 -1
  137. package/dist/esm/expect.d.ts.map +1 -1
  138. package/dist/esm/expect.js +2 -2
  139. package/dist/esm/expect.js.map +1 -1
  140. package/dist/esm/guards.d.ts +96 -5
  141. package/dist/esm/guards.d.ts.map +1 -1
  142. package/dist/esm/guards.js +98 -21
  143. package/dist/esm/guards.js.map +1 -1
  144. package/dist/esm/index.d.ts +146 -51
  145. package/dist/esm/index.d.ts.map +1 -1
  146. package/dist/esm/index.js.map +1 -1
  147. package/dist/esm/schema.d.ts +84 -18
  148. package/dist/esm/schema.d.ts.map +1 -1
  149. package/dist/esm/schema.js +107 -22
  150. package/dist/esm/schema.js.map +1 -1
  151. package/dist/esm/types.d.ts +171 -9
  152. package/dist/esm/types.d.ts.map +1 -1
  153. package/dist/esm/use.d.ts.map +1 -1
  154. package/dist/esm/use.js +15 -1
  155. package/dist/esm/use.js.map +1 -1
  156. package/dist/esm/util.d.ts +66 -50
  157. package/dist/esm/util.d.ts.map +1 -1
  158. package/dist/esm/util.js +153 -154
  159. package/dist/esm/util.js.map +1 -1
  160. package/dist/esm/value-to-schema.d.ts +122 -0
  161. package/dist/esm/value-to-schema.d.ts.map +1 -0
  162. package/dist/esm/value-to-schema.js +325 -0
  163. package/dist/esm/value-to-schema.js.map +1 -0
  164. package/package.json +16 -13
  165. package/src/assertion/assertion-async.ts +113 -3
  166. package/src/assertion/assertion-sync.ts +5 -2
  167. package/src/assertion/assertion-types.ts +14 -4
  168. package/src/assertion/assertion.ts +2 -17
  169. package/src/assertion/impl/async.ts +137 -93
  170. package/src/assertion/impl/callback.ts +882 -0
  171. package/src/assertion/impl/index.ts +1 -1
  172. package/src/assertion/impl/sync-basic.ts +5 -2
  173. package/src/assertion/impl/sync-collection.ts +3 -3
  174. package/src/assertion/impl/sync-esoteric.ts +2 -2
  175. package/src/assertion/impl/sync-parametric.ts +47 -54
  176. package/src/assertion/impl/sync.ts +3 -0
  177. package/src/bootstrap.ts +1 -2
  178. package/src/constant.ts +10 -0
  179. package/src/error.ts +57 -3
  180. package/src/expect.ts +6 -2
  181. package/src/guards.ts +125 -18
  182. package/src/index.ts +3 -0
  183. package/src/schema.ts +121 -23
  184. package/src/types.ts +205 -10
  185. package/src/use.ts +22 -0
  186. package/src/util.ts +168 -223
  187. package/src/value-to-schema.ts +489 -0
@@ -0,0 +1,489 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ import {
4
+ isExpectItExecutor,
5
+ isNonNullObject,
6
+ isObject,
7
+ isPromiseLike,
8
+ isString,
9
+ isZodType,
10
+ } from './guards.js';
11
+ import {
12
+ RegExpSchema,
13
+ StrongMapSchema,
14
+ StrongSetSchema,
15
+ WrappedPromiseLikeSchema,
16
+ } from './schema.js';
17
+
18
+ /**
19
+ * Recursively converts an arbitrary value to a Zod v4 schema that would
20
+ * validate values with the same structure.
21
+ *
22
+ * This function analyzes the runtime value and generates a corresponding Zod
23
+ * schema that captures the value's structure and type information. It handles
24
+ * primitives, objects, arrays, functions, and various built-in types, with
25
+ * support for circular reference detection.
26
+ *
27
+ * @example
28
+ *
29
+ * ```typescript
30
+ * // Primitive types
31
+ * valueToSchema('hello'); // z.string()
32
+ * valueToSchema(42); // z.number()
33
+ * valueToSchema(true); // z.boolean()
34
+ *
35
+ * // Objects
36
+ * valueToSchema({ name: 'John', age: 30 });
37
+ * // z.object({ name: z.string(), age: z.number() })
38
+ *
39
+ * // Arrays
40
+ * valueToSchema(['a', 'b', 'c']); // z.array(z.string())
41
+ * valueToSchema([1, 'mixed']); // z.array(z.union([z.number(), z.string()]))
42
+ *
43
+ * // Nested structures
44
+ * valueToSchema({ users: [{ name: 'John' }] });
45
+ * // z.object({ users: z.array(z.object({ name: z.string() })) })
46
+ * ```
47
+ *
48
+ * @param value - The value to convert to a schema
49
+ * @param options - Configuration options for schema generation
50
+ * @param visited - Internal WeakSet for circular reference detection
51
+ * @returns A Zod schema that validates values matching the input's structure.
52
+ * This value is unfortunately untyped due to the complexity involved. But the
53
+ * schema works!
54
+ */
55
+
56
+ export const valueToSchema = (
57
+ value: unknown,
58
+ options: ValueToSchemaOptions = {},
59
+ visited = new WeakSet<object>(),
60
+ ): z.ZodType<any> => {
61
+ const {
62
+ _currentDepth = 0,
63
+ literalEmptyObjects = false,
64
+ literalPrimitives = false,
65
+ literalRegExp = false,
66
+ literalTuples = false,
67
+ maxDepth = 10,
68
+ noMixedArrays = false,
69
+ strict = false,
70
+ } = options;
71
+
72
+ // Prevent infinite recursion
73
+ if (_currentDepth >= maxDepth) {
74
+ return z.unknown();
75
+ }
76
+
77
+ // Handle primitives
78
+ if (value === null) {
79
+ return z.null();
80
+ }
81
+
82
+ if (value === undefined) {
83
+ return literalPrimitives
84
+ ? z.custom<undefined>((val: unknown) => val === undefined, {
85
+ message: 'Expected undefined',
86
+ })
87
+ : z.undefined();
88
+ }
89
+ if (Number.isNaN(value as number)) {
90
+ return z.nan();
91
+ }
92
+ if (value === Infinity || value === -Infinity) {
93
+ return z.literal(value);
94
+ }
95
+
96
+ const valueType = typeof value;
97
+
98
+ switch (valueType) {
99
+ case 'bigint':
100
+ return literalPrimitives ? z.literal(value as bigint) : z.bigint();
101
+ case 'boolean':
102
+ return literalPrimitives ? z.literal(value as boolean) : z.boolean();
103
+ case 'function':
104
+ // Check if this is an ExpectItExecutor
105
+ if (isExpectItExecutor(value)) {
106
+ // Only allow nested assertions when strict is false (e.g., "to satisfy" semantics)
107
+ if (strict) {
108
+ throw new TypeError(
109
+ 'ExpectItExecutor (expect.it) functions are not allowed in strict mode. ' +
110
+ 'Use "to satisfy" assertions for nested expectations.',
111
+ );
112
+ }
113
+ // Return a schema that executes the ExpectItExecutor when validated
114
+ return z.custom<unknown>(
115
+ (subject: unknown) => {
116
+ try {
117
+ value(subject);
118
+ return true;
119
+ } catch {
120
+ return false;
121
+ }
122
+ },
123
+ {
124
+ message: 'Failed expect.it assertion',
125
+ },
126
+ );
127
+ }
128
+ return z.function();
129
+ case 'number':
130
+ return literalPrimitives ? z.literal(value as number) : z.number();
131
+ case 'string':
132
+ return literalPrimitives ? z.literal(value as string) : z.string();
133
+ case 'symbol':
134
+ return z.symbol();
135
+ }
136
+
137
+ // Handle objects
138
+ if (typeof value === 'object' && value !== null) {
139
+ // Check for circular references
140
+ if (visited.has(value)) {
141
+ // Return a recursive schema reference or unknown for circular refs
142
+ return z.unknown();
143
+ }
144
+
145
+ visited.add(value);
146
+
147
+ try {
148
+ // Check for objects with own __proto__ property - these can cause unexpected behavior
149
+ if (Object.hasOwn(value, '__proto__')) {
150
+ throw new TypeError(
151
+ 'Objects with an own "__proto__" property are not supported by valueToSchema',
152
+ );
153
+ }
154
+
155
+ // Handle built-in object types
156
+ if (value instanceof Date) {
157
+ // Check if it's a valid date
158
+ if (isNaN(value.getTime())) {
159
+ // For invalid dates, use a literal or custom validator
160
+ return z.custom<Date>(
161
+ (val) => val instanceof Date && isNaN(val.getTime()),
162
+ {
163
+ message: 'Expected an invalid Date',
164
+ },
165
+ );
166
+ }
167
+ return z.date();
168
+ }
169
+
170
+ if (value instanceof RegExp) {
171
+ if (literalRegExp) {
172
+ return RegExpSchema;
173
+ }
174
+ return z.coerce.string().regex(value);
175
+ }
176
+
177
+ if (value instanceof Map) {
178
+ return StrongMapSchema;
179
+ }
180
+
181
+ if (value instanceof Set) {
182
+ return StrongSetSchema;
183
+ }
184
+
185
+ if (value instanceof WeakMap) {
186
+ return z.instanceof(WeakMap);
187
+ }
188
+
189
+ if (value instanceof WeakSet) {
190
+ return z.instanceof(WeakSet);
191
+ }
192
+
193
+ if (value instanceof Error) {
194
+ return z.instanceof(Error);
195
+ }
196
+
197
+ if (isPromiseLike(value)) {
198
+ return WrappedPromiseLikeSchema;
199
+ }
200
+
201
+ // Handle arrays
202
+ if (Array.isArray(value)) {
203
+ // For arrays, we need to preserve undefined values while allowing
204
+ // other elements to use the original literalPrimitives setting
205
+ const filteredValue = value; // Always process all elements
206
+
207
+ if (filteredValue.length === 0) {
208
+ // For empty arrays, use z.tuple() if literalTuples is enabled
209
+ if (literalTuples) {
210
+ return z.tuple([]);
211
+ }
212
+ return z.array(z.never());
213
+ }
214
+
215
+ const elementSchemas = filteredValue.map((item) => {
216
+ // Use literal mode for undefined values to preserve them exactly,
217
+ // but use the original setting for other values
218
+ const itemLiteralPrimitives =
219
+ item === undefined ? true : literalPrimitives;
220
+
221
+ return valueToSchema(
222
+ item,
223
+ {
224
+ ...options,
225
+ _currentDepth: _currentDepth + 1,
226
+ literalPrimitives: itemLiteralPrimitives,
227
+ },
228
+ visited,
229
+ );
230
+ });
231
+
232
+ // Use z.tuple() if literalTuples is enabled
233
+ if (literalTuples) {
234
+ return z.tuple(elementSchemas as [z.ZodType, ...z.ZodType[]]);
235
+ }
236
+
237
+ if (!noMixedArrays) {
238
+ // Helper function to generate structural keys for schemas
239
+ const getSchemaKey = <T extends z.core.SomeType | z.ZodType>(
240
+ zodType: T,
241
+ ): string => {
242
+ const schema = zodType as z.ZodType;
243
+ if (isZodType(schema, 'literal')) {
244
+ return `${schema.constructor.name}:${String(schema.def.values)}`;
245
+ }
246
+
247
+ if (isZodType(schema, 'array')) {
248
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
249
+ const elementKey = getSchemaKey((schema.def as any).element);
250
+ return `ZodArray<${elementKey}>`;
251
+ }
252
+
253
+ if (isZodType(schema, 'object')) {
254
+ // For objects, create a key based on the property keys and their types
255
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
256
+ const shape = (schema.def as any).shape as Record<
257
+ string,
258
+ z.ZodType
259
+ >;
260
+ const shapeKeys = Object.keys(shape)
261
+ .sort()
262
+ .map((key) => {
263
+ const propSchema = shape[key]!;
264
+ return `${key}:${getSchemaKey(propSchema)}`;
265
+ });
266
+ return `ZodObject<{${shapeKeys.join(',')}}>`;
267
+ }
268
+
269
+ if (isZodType(schema, 'union')) {
270
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
271
+ const optionKeys = ((schema.def as any).options as z.ZodType[])
272
+ .map((option) => getSchemaKey(option))
273
+ .sort();
274
+ return `ZodUnion<[${optionKeys.join(',')}]>`;
275
+ }
276
+
277
+ // For other types, use the constructor name
278
+ return schema.constructor.name;
279
+ };
280
+
281
+ const seenSchemaKeys = new Set<string>();
282
+ const uniqueSchemas: z.ZodType[] = [];
283
+
284
+ for (const schema of elementSchemas) {
285
+ const schemaKey = getSchemaKey(schema);
286
+
287
+ if (!seenSchemaKeys.has(schemaKey)) {
288
+ seenSchemaKeys.add(schemaKey);
289
+ uniqueSchemas.push(schema);
290
+ }
291
+ }
292
+
293
+ if (uniqueSchemas.length === 1) {
294
+ return z.array(uniqueSchemas[0]!);
295
+ } else {
296
+ return z.array(
297
+ z.union(uniqueSchemas as [z.ZodType, z.ZodType, ...z.ZodType[]]),
298
+ );
299
+ }
300
+ } else {
301
+ // Use the first element's schema for all elements
302
+ return z.array(elementSchemas[0]!);
303
+ }
304
+ }
305
+
306
+ // Handle plain objects
307
+ if (isNonNullObject(value)) {
308
+ const schemaShape: Record<string, z.ZodType<any>> = {};
309
+ const undefinedKeys: string[] = [];
310
+
311
+ for (const [key, val] of Object.entries(value)) {
312
+ if (isString(key)) {
313
+ // Skip undefined values unless we're in literalPrimitives mode
314
+ // This prevents objects with only undefined values from matching any object
315
+ if (val === undefined && !literalPrimitives) {
316
+ continue;
317
+ }
318
+
319
+ if (val === undefined && literalPrimitives) {
320
+ // Track keys that should have undefined values
321
+ undefinedKeys.push(key);
322
+ schemaShape[key] = z.undefined();
323
+ } else {
324
+ schemaShape[key] = valueToSchema(
325
+ val,
326
+ {
327
+ ...options,
328
+ _currentDepth: _currentDepth + 1,
329
+ },
330
+ visited,
331
+ );
332
+ }
333
+ }
334
+ }
335
+
336
+ // Create the base object schema
337
+ const baseSchema = strict
338
+ ? z.strictObject(schemaShape)
339
+ : z.looseObject(schemaShape);
340
+
341
+ // If we have undefined keys in literalPrimitives mode, add validation to ensure they exist
342
+ if (undefinedKeys.length > 0 && literalPrimitives) {
343
+ return baseSchema.superRefine((data, ctx) => {
344
+ if (typeof data !== 'object' || data === null) {
345
+ ctx.addIssue({
346
+ code: z.ZodIssueCode.custom,
347
+ message: 'Expected an object',
348
+ });
349
+ return;
350
+ }
351
+
352
+ const obj = data as Record<string, unknown>;
353
+ for (const key of undefinedKeys) {
354
+ if (!Object.hasOwn(obj, key)) {
355
+ ctx.addIssue({
356
+ code: z.ZodIssueCode.custom,
357
+ message: `Expected property "${key}" to exist with value undefined`,
358
+ path: [key],
359
+ });
360
+ }
361
+ }
362
+ });
363
+ }
364
+
365
+ // Check if this is an empty object and literalEmptyObjects is enabled
366
+ if (Object.keys(schemaShape).length === 0 && literalEmptyObjects) {
367
+ // Create a schema that only matches empty objects
368
+ return z.custom<Record<string, never>>(
369
+ (val) => isObject(val) && Object.keys(val).length === 0,
370
+ {
371
+ message: 'Expected an empty object with no own properties',
372
+ },
373
+ );
374
+ }
375
+
376
+ return baseSchema;
377
+ }
378
+
379
+ // Handle other object types (ArrayBuffer, etc.)
380
+ return z.custom<object>(
381
+ (val) => typeof val === 'object' && val !== null,
382
+ { message: 'Expected an object' },
383
+ );
384
+ } finally {
385
+ visited.delete(value);
386
+ }
387
+ }
388
+
389
+ // Fallback for unknown types
390
+ return z.unknown();
391
+ };
392
+
393
+ /**
394
+ * Options for {@link valueToSchema}
395
+ */
396
+ export interface ValueToSchemaOptions {
397
+ /**
398
+ * Current depth (internal)
399
+ *
400
+ * @internal
401
+ */
402
+ _currentDepth?: number;
403
+
404
+ /**
405
+ * If `true`, treat empty objects `{}` as literal empty objects that only
406
+ * match objects with zero own properties
407
+ *
408
+ * @defaultValue false
409
+ */
410
+ literalEmptyObjects?: boolean;
411
+
412
+ /**
413
+ * If `true`, use literal schema for primitive values instead of type schema
414
+ *
415
+ * @defaultValue false
416
+ */
417
+ literalPrimitives?: boolean;
418
+
419
+ /**
420
+ * If `true`, treat `RegExp` literals as `RegExp` literals; otherwise treat as
421
+ * strings and attempt match
422
+ *
423
+ * @defaultValue false
424
+ */
425
+ literalRegExp?: boolean;
426
+
427
+ /**
428
+ * If `true`, treat arrays as tuples wherever possible.
429
+ *
430
+ * Implies `false` for {@link noMixedArrays}.
431
+ *
432
+ * @defaultValue false
433
+ */
434
+ literalTuples?: boolean;
435
+
436
+ /**
437
+ * Maximum nesting depth to prevent stack overflow
438
+ *
439
+ * @defaultValue 10
440
+ */
441
+ maxDepth?: number;
442
+
443
+ /**
444
+ * Whether to allow mixed types in arrays
445
+ *
446
+ * If {@link literalTuples} is `true`, this option is ignored and treated as
447
+ * `false`.
448
+ *
449
+ * @defaultValue false
450
+ */
451
+ noMixedArrays?: boolean;
452
+
453
+ /**
454
+ * If `true`, will disallow unknown properties in parsed objects
455
+ *
456
+ * @defaultValue false
457
+ */
458
+ strict?: boolean;
459
+ }
460
+
461
+ /**
462
+ * Predefined options for {@link valueToSchema} optimized for object satisfaction
463
+ * checks.
464
+ *
465
+ * Uses literal primitives and tuples for exact matching while allowing extra
466
+ * properties.
467
+ */
468
+ export const valueToSchemaOptionsForSatisfies = Object.freeze({
469
+ literalEmptyObjects: true,
470
+ literalPrimitives: true,
471
+ literalRegExp: false,
472
+ literalTuples: true,
473
+ strict: false,
474
+ } as const) satisfies ValueToSchemaOptions;
475
+
476
+ /**
477
+ * Predefined options for {@link valueToSchema} optimized for deep equality
478
+ * checks.
479
+ *
480
+ * Uses literal primitives, regexp, and tuples with strict validation for exact
481
+ * matching.
482
+ */
483
+ export const valueToSchemaOptionsForDeepEqual = Object.freeze({
484
+ literalEmptyObjects: true,
485
+ literalPrimitives: true,
486
+ literalRegExp: true,
487
+ literalTuples: true,
488
+ strict: true,
489
+ } as const) satisfies ValueToSchemaOptions;