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