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
package/src/util.ts CHANGED
@@ -1,11 +1,5 @@
1
1
  /**
2
- * Utility functions for object satisfaction and shape validation.
3
- *
4
- * This module provides core utility functions for checking if objects satisfy
5
- * expected shapes, including `satisfies` for partial matching,
6
- * `exhaustivelySatisfies` for exact matching, and `shallowSatisfiesShape` for
7
- * converting shapes to Zod schemas. All functions handle circular references
8
- * safely.
2
+ * Utility functions.
9
3
  *
10
4
  * @category API
11
5
  * @example
@@ -18,254 +12,205 @@
18
12
  */
19
13
 
20
14
  import { type StringKeyOf } from 'type-fest';
21
- import { z } from 'zod/v4';
22
-
23
- import { isNonNullObject, isPromiseLike, isString } from './guards.js';
24
- import {
25
- FunctionSchema,
26
- RegExpSchema,
27
- StrongMapSchema,
28
- StrongSetSchema,
29
- WrappedPromiseLikeSchema,
30
- } from './schema.js';
31
15
 
32
- export function keyBy<
33
- const T extends readonly Record<PropertyKey, any>[],
34
- K extends StringKeyOf<T[number]>,
35
- >(collection: T, key: K): Record<string, T[number]> {
36
- const result = {} as Record<string, T[number]>;
37
-
38
- for (const item of collection) {
39
- const keyValue = item[key];
40
- if (
41
- typeof keyValue === 'string' ||
42
- typeof keyValue === 'number' ||
43
- typeof keyValue === 'symbol'
44
- ) {
45
- result[String(keyValue)] = item;
46
- }
47
- }
48
-
49
- return result;
50
- }
16
+ export * from './value-to-schema.js';
51
17
 
52
18
  /**
53
- * Recursively converts an arbitrary value to a Zod v4 schema that would
54
- * validate values with the same structure.
19
+ * _Recursively_ searches within an object, array, or any nested structure to
20
+ * find whether a specific key exists.
55
21
  *
56
- * This function analyzes the runtime value and generates a corresponding Zod
57
- * schema that captures the value's structure and type information. It handles
58
- * primitives, objects, arrays, functions, and various built-in types, with
59
- * support for circular reference detection.
22
+ * Handles circular references by tracking visited objects to prevent infinite
23
+ * recursion.
60
24
  *
61
25
  * @example
62
26
  *
63
- * ```typescript
64
- * // Primitive types
65
- * valueToSchema('hello'); // z.string()
66
- * valueToSchema(42); // z.number()
67
- * valueToSchema(true); // z.boolean()
68
- *
69
- * // Objects
70
- * valueToSchema({ name: 'John', age: 30 });
71
- * // z.object({ name: z.string(), age: z.number() })
72
- *
73
- * // Arrays
74
- * valueToSchema(['a', 'b', 'c']); // z.array(z.string())
75
- * valueToSchema([1, 'mixed']); // z.array(z.union([z.number(), z.string()]))
27
+ * ```ts
28
+ * const obj = { a: 1, b: { c: 2, d: [{ e: 3 }] } };
76
29
  *
77
- * // Nested structures
78
- * valueToSchema({ users: [{ name: 'John' }] });
79
- * // z.object({ users: z.array(z.object({ name: z.string() })) })
30
+ * hasKey(obj, 'c'); // true
31
+ * hasKey(obj, 'e'); // true
32
+ * hasKey(obj, 'x'); // false (key not found)
80
33
  * ```
81
34
  *
82
- * @param value - The value to convert to a schema
83
- * @param options - Configuration options for schema generation
84
- * @param visited - Internal WeakSet for circular reference detection
85
- * @returns A Zod schema that validates values matching the input's structure
35
+ * @param obj The object, array, or value to search within
36
+ * @param key The key to search for
37
+ * @param visited Internal set for circular reference detection
38
+ * @returns True if the key is found anywhere in the structure, false otherwise
86
39
  */
87
- export const valueToSchema = (
88
- value: unknown,
89
- options: {
90
- /** Current depth (internal) */
91
- _currentDepth?: number;
92
- /** Whether to allow mixed types in arrays (default: true) */
93
- allowMixedArrays?: boolean;
94
- /** If `true`, use `z.literal()` for primitive values instead of type schemas */
95
- literalPrimitives?: boolean;
96
- /**
97
- * If `true`, treat `RegExp` literals as `RegExp` literals; otherwise treat
98
- * as strings and attempt match
99
- */
100
- literalRegExp?: boolean;
101
- /** Maximum nesting depth to prevent stack overflow (default: 10) */
102
- maxDepth?: number;
103
- /** If `true`, will disallow unknown properties in objects */
104
- strict?: boolean;
105
- } = {},
40
+ export function hasKey(
41
+ obj: unknown,
42
+ key: PropertyKey,
106
43
  visited = new WeakSet<object>(),
107
- ): z.ZodType => {
108
- const {
109
- _currentDepth = 0,
110
- allowMixedArrays = true,
111
- literalPrimitives = false,
112
- literalRegExp = false,
113
- maxDepth = 10,
114
- strict = false,
115
- } = options;
116
-
117
- // Prevent infinite recursion
118
- if (_currentDepth >= maxDepth) {
119
- return z.unknown();
44
+ ): boolean {
45
+ // Handle primitives that can't contain keys
46
+ if (typeof obj !== 'object' || obj === null) {
47
+ return false;
120
48
  }
121
49
 
122
- // Handle primitives
123
- if (value === null) {
124
- return z.null();
50
+ // Prevent infinite recursion with circular references
51
+ if (visited.has(obj)) {
52
+ return false;
125
53
  }
54
+ visited.add(obj);
126
55
 
127
- if (value === undefined) {
128
- return z.undefined();
129
- }
130
- if (Number.isNaN(value as number)) {
131
- return z.nan();
132
- }
133
- if (value === Infinity || value === -Infinity) {
134
- return z.literal(value as any);
135
- }
136
-
137
- const valueType = typeof value;
138
-
139
- switch (valueType) {
140
- case 'bigint':
141
- return literalPrimitives ? z.literal(value as bigint) : z.bigint();
142
- case 'boolean':
143
- return literalPrimitives ? z.literal(value as boolean) : z.boolean();
144
- case 'function':
145
- return FunctionSchema;
146
- case 'number':
147
- return literalPrimitives ? z.literal(value as number) : z.number();
148
- case 'string':
149
- return literalPrimitives ? z.literal(value as string) : z.string();
150
- case 'symbol':
151
- return z.symbol();
152
- }
153
-
154
- // Handle objects
155
- if (typeof value === 'object' && value !== null) {
156
- // Check for circular references
157
- if (visited.has(value)) {
158
- // Return a recursive schema reference or unknown for circular refs
159
- return z.unknown();
56
+ try {
57
+ // Check if this object has the key
58
+ if (Object.hasOwn(obj, key)) {
59
+ return true;
160
60
  }
161
61
 
162
- visited.add(value);
163
-
164
- try {
165
- // Handle built-in object types
166
- if (value instanceof Date) {
167
- return z.date();
168
- }
169
-
170
- if (value instanceof RegExp) {
171
- if (literalRegExp) {
172
- return RegExpSchema;
62
+ // Recursively search in object/array values
63
+ if (Array.isArray(obj)) {
64
+ // For arrays, search in each element
65
+ for (const item of obj) {
66
+ if (hasKey(item, key, visited)) {
67
+ return true;
173
68
  }
174
- return z.coerce.string().regex(value);
175
- }
176
-
177
- if (value instanceof Map) {
178
- return StrongMapSchema;
179
69
  }
180
-
181
- if (value instanceof Set) {
182
- return StrongSetSchema;
70
+ } else {
71
+ // For objects, search in each property value
72
+ for (const propValue of Object.values(obj)) {
73
+ if (hasKey(propValue, key, visited)) {
74
+ return true;
75
+ }
183
76
  }
77
+ }
184
78
 
185
- if (value instanceof WeakMap) {
186
- return z.instanceof(WeakMap);
187
- }
79
+ return false;
80
+ } finally {
81
+ visited.delete(obj);
82
+ }
83
+ }
188
84
 
189
- if (value instanceof WeakSet) {
190
- return z.instanceof(WeakSet);
191
- }
85
+ /**
86
+ * _Recursively_ searches within an object, array, or any nested structure to
87
+ * find whether a specific value exists.
88
+ *
89
+ * Uses strict equality (===) to compare values, with special handling for empty
90
+ * objects. Handles circular references by tracking visited objects to prevent
91
+ * infinite recursion.
92
+ *
93
+ * @example
94
+ *
95
+ * ```ts
96
+ * const obj = { a: 1, b: { c: 2, d: [{ e: 3 }] }, empty: {} };
97
+ *
98
+ * hasValue(obj, 2); // true (found in obj.b.c)
99
+ * hasValue(obj, 3); // true (found in obj.b.d[0].e)
100
+ * hasValue(obj, {}); // true (found in obj.empty, matches empty objects)
101
+ * hasValue(obj, '1'); // false (strict equality, 1 !== '1')
102
+ * hasValue(obj, 999); // false (value not found)
103
+ * ```
104
+ *
105
+ * @param obj The object, array, or value to search within
106
+ * @param value The value to search for (using strict equality, with special
107
+ * empty object handling)
108
+ * @param visited Internal set for circular reference detection
109
+ * @returns True if the value is found anywhere in the structure, false
110
+ * otherwise
111
+ */
112
+ export function hasValue(
113
+ obj: unknown,
114
+ value: unknown,
115
+ visited = new WeakSet<object>(),
116
+ ): boolean {
117
+ // Direct value comparison
118
+ if (obj === value) {
119
+ return true;
120
+ }
192
121
 
193
- if (value instanceof Error) {
194
- return z.instanceof(Error);
195
- }
122
+ // Special case: Check for empty objects
123
+ if (
124
+ typeof obj === 'object' &&
125
+ obj !== null &&
126
+ !Array.isArray(obj) &&
127
+ typeof value === 'object' &&
128
+ value !== null &&
129
+ !Array.isArray(value)
130
+ ) {
131
+ const objKeys = Object.keys(obj);
132
+ const valueKeys = Object.keys(value);
133
+
134
+ // Both are empty objects
135
+ if (objKeys.length === 0 && valueKeys.length === 0) {
136
+ return true;
137
+ }
138
+ }
196
139
 
197
- if (isPromiseLike(value)) {
198
- return WrappedPromiseLikeSchema;
199
- }
140
+ // Handle primitives that can't contain nested values
141
+ if (typeof obj !== 'object' || obj === null) {
142
+ return false;
143
+ }
200
144
 
201
- // Handle arrays
202
- if (Array.isArray(value)) {
203
- if (value.length === 0) {
204
- return z.array(z.unknown());
145
+ // Prevent infinite recursion with circular references
146
+ if (visited.has(obj)) {
147
+ return false;
148
+ }
149
+ visited.add(obj);
150
+
151
+ try {
152
+ // Recursively search in object/array values
153
+ if (Array.isArray(obj)) {
154
+ // For arrays, search in each element
155
+ for (const item of obj) {
156
+ if (hasValue(item, value, visited)) {
157
+ return true;
205
158
  }
206
-
207
- const elementSchemas = value.map((item) =>
208
- valueToSchema(
209
- item,
210
- {
211
- ...options,
212
- _currentDepth: _currentDepth + 1,
213
- },
214
- visited,
215
- ),
216
- );
217
-
218
- if (allowMixedArrays) {
219
- // Create a union of all unique element types
220
- const uniqueSchemas = Array.from(
221
- new Set(elementSchemas.map((schema) => schema.constructor.name)),
222
- ).map((_, index) => elementSchemas[index]);
223
-
224
- if (uniqueSchemas.length === 1) {
225
- return z.array(uniqueSchemas[0]!);
226
- } else {
227
- return z.array(
228
- z.union(uniqueSchemas as [z.ZodType, z.ZodType, ...z.ZodType[]]),
229
- );
230
- }
231
- } else {
232
- // Use the first element's schema for all elements
233
- return z.array(elementSchemas[0]!);
159
+ }
160
+ } else {
161
+ // For objects, search in each property value
162
+ for (const propValue of Object.values(obj)) {
163
+ if (hasValue(propValue, value, visited)) {
164
+ return true;
234
165
  }
235
166
  }
167
+ }
236
168
 
237
- // Handle plain objects
238
- if (isNonNullObject(value)) {
239
- const schemaShape: Record<string, z.ZodType<any>> = {};
240
-
241
- for (const [key, val] of Object.entries(value)) {
242
- if (isString(key)) {
243
- schemaShape[key] = valueToSchema(
244
- val,
245
- {
246
- ...options,
247
- _currentDepth: _currentDepth + 1,
248
- },
249
- visited,
250
- );
251
- }
252
- }
169
+ return false;
170
+ } finally {
171
+ visited.delete(obj);
172
+ }
173
+ }
253
174
 
254
- return strict
255
- ? z.strictObject(schemaShape)
256
- : z.looseObject(schemaShape);
257
- }
175
+ /**
176
+ * _Recursively_ searches for a key-value pair within an object or array.
177
+ *
178
+ * Uses strict equality (===) to compare values. Handles circular references by
179
+ * tracking visited objects to prevent infinite recursion.
180
+ *
181
+ * @example
182
+ *
183
+ * ```ts
184
+ * const obj = { a: 1, b: { c: 2, d: [{ e: 3 }] } };
185
+ *
186
+ * hasKeyValue(obj, 'c', 2); // true
187
+ * hasKeyValue(obj, 'e', 3); // true
188
+ * hasKeyValue(obj, 'a', '1'); // false (strict equality)
189
+ * hasKeyValue(obj, 'x', 1); // false (key not found)
190
+ * /**
191
+ * Maps an array of objects to an object keyed by the specified key.
192
+ *
193
+ * @param collection Array of objects
194
+ * @param key Name of key
195
+ * @returns Object mapping key values to objects
196
+ * ```
197
+ */
198
+ export function keyBy<
199
+ const T extends readonly Record<PropertyKey, any>[],
200
+ K extends StringKeyOf<T[number]>,
201
+ >(collection: T, key: K): Record<string, T[number]> {
202
+ const result = {} as Record<string, T[number]>;
258
203
 
259
- // Handle other object types (ArrayBuffer, etc.)
260
- return z.custom<object>(
261
- (val) => typeof val === 'object' && val !== null,
262
- { message: 'Expected an object' },
263
- );
264
- } finally {
265
- visited.delete(value);
204
+ for (const item of collection) {
205
+ const keyValue = item[key];
206
+ if (
207
+ typeof keyValue === 'string' ||
208
+ typeof keyValue === 'number' ||
209
+ typeof keyValue === 'symbol'
210
+ ) {
211
+ result[String(keyValue)] = item;
266
212
  }
267
213
  }
268
214
 
269
- // Fallback for unknown types
270
- return z.unknown();
271
- };
215
+ return result;
216
+ }