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.
- package/CHANGELOG.md +27 -0
- package/README.md +35 -11
- package/dist/commonjs/assertion/assertion-async.d.ts +2 -1
- package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
- package/dist/commonjs/assertion/assertion-async.js +84 -2
- package/dist/commonjs/assertion/assertion-async.js.map +1 -1
- package/dist/commonjs/assertion/assertion-sync.d.ts +1 -1
- package/dist/commonjs/assertion/assertion-sync.d.ts.map +1 -1
- package/dist/commonjs/assertion/assertion-sync.js +5 -1
- package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
- package/dist/commonjs/assertion/assertion-types.d.ts +6 -2
- package/dist/commonjs/assertion/assertion-types.d.ts.map +1 -1
- package/dist/commonjs/assertion/assertion.d.ts +1 -1
- package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
- package/dist/commonjs/assertion/assertion.js +1 -14
- package/dist/commonjs/assertion/assertion.js.map +1 -1
- package/dist/commonjs/assertion/impl/async.d.ts +122 -21
- package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/async.js +118 -90
- package/dist/commonjs/assertion/impl/async.js.map +1 -1
- package/dist/commonjs/assertion/impl/callback.d.ts +104 -0
- package/dist/commonjs/assertion/impl/callback.d.ts.map +1 -0
- package/dist/commonjs/assertion/impl/callback.js +694 -0
- package/dist/commonjs/assertion/impl/callback.js.map +1 -0
- package/dist/commonjs/assertion/impl/index.d.ts +1 -1
- package/dist/commonjs/assertion/impl/index.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/index.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync-basic.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/sync-basic.js +1 -1
- package/dist/commonjs/assertion/impl/sync-basic.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync-collection.d.ts +1 -1
- package/dist/commonjs/assertion/impl/sync-collection.js +3 -3
- package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync-esoteric.js +1 -1
- package/dist/commonjs/assertion/impl/sync-esoteric.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync-parametric.d.ts +22 -28
- package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/sync-parametric.js +35 -50
- package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync.d.ts +68 -30
- package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/sync.js +4 -1
- package/dist/commonjs/assertion/impl/sync.js.map +1 -1
- package/dist/commonjs/bootstrap.d.ts +147 -52
- package/dist/commonjs/bootstrap.d.ts.map +1 -1
- package/dist/commonjs/bootstrap.js +2 -3
- package/dist/commonjs/bootstrap.js.map +1 -1
- package/dist/commonjs/constant.d.ts +1 -1
- package/dist/commonjs/constant.d.ts.map +1 -1
- package/dist/commonjs/constant.js +8 -1
- package/dist/commonjs/constant.js.map +1 -1
- package/dist/commonjs/error.d.ts +22 -2
- package/dist/commonjs/error.d.ts.map +1 -1
- package/dist/commonjs/error.js +44 -4
- package/dist/commonjs/error.js.map +1 -1
- package/dist/commonjs/expect.d.ts.map +1 -1
- package/dist/commonjs/expect.js +1 -1
- package/dist/commonjs/expect.js.map +1 -1
- package/dist/commonjs/guards.d.ts +96 -5
- package/dist/commonjs/guards.d.ts.map +1 -1
- package/dist/commonjs/guards.js +104 -25
- package/dist/commonjs/guards.js.map +1 -1
- package/dist/commonjs/index.d.ts +146 -51
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/schema.d.ts +84 -18
- package/dist/commonjs/schema.d.ts.map +1 -1
- package/dist/commonjs/schema.js +107 -22
- package/dist/commonjs/schema.js.map +1 -1
- package/dist/commonjs/types.d.ts +171 -9
- package/dist/commonjs/types.d.ts.map +1 -1
- package/dist/commonjs/use.d.ts.map +1 -1
- package/dist/commonjs/use.js +15 -1
- package/dist/commonjs/use.js.map +1 -1
- package/dist/commonjs/util.d.ts +66 -50
- package/dist/commonjs/util.d.ts.map +1 -1
- package/dist/commonjs/util.js +169 -156
- package/dist/commonjs/util.js.map +1 -1
- package/dist/commonjs/value-to-schema.d.ts +122 -0
- package/dist/commonjs/value-to-schema.d.ts.map +1 -0
- package/dist/commonjs/value-to-schema.js +329 -0
- package/dist/commonjs/value-to-schema.js.map +1 -0
- package/dist/esm/assertion/assertion-async.d.ts +2 -1
- package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
- package/dist/esm/assertion/assertion-async.js +85 -3
- package/dist/esm/assertion/assertion-async.js.map +1 -1
- package/dist/esm/assertion/assertion-sync.d.ts +1 -1
- package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
- package/dist/esm/assertion/assertion-sync.js +6 -2
- package/dist/esm/assertion/assertion-sync.js.map +1 -1
- package/dist/esm/assertion/assertion-types.d.ts +6 -2
- package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
- package/dist/esm/assertion/assertion.d.ts +1 -1
- package/dist/esm/assertion/assertion.d.ts.map +1 -1
- package/dist/esm/assertion/assertion.js +1 -14
- package/dist/esm/assertion/assertion.js.map +1 -1
- package/dist/esm/assertion/impl/async.d.ts +122 -21
- package/dist/esm/assertion/impl/async.d.ts.map +1 -1
- package/dist/esm/assertion/impl/async.js +118 -90
- package/dist/esm/assertion/impl/async.js.map +1 -1
- package/dist/esm/assertion/impl/callback.d.ts +104 -0
- package/dist/esm/assertion/impl/callback.d.ts.map +1 -0
- package/dist/esm/assertion/impl/callback.js +691 -0
- package/dist/esm/assertion/impl/callback.js.map +1 -0
- package/dist/esm/assertion/impl/index.d.ts +1 -1
- package/dist/esm/assertion/impl/index.d.ts.map +1 -1
- package/dist/esm/assertion/impl/index.js +1 -1
- package/dist/esm/assertion/impl/index.js.map +1 -1
- package/dist/esm/assertion/impl/sync-basic.d.ts.map +1 -1
- package/dist/esm/assertion/impl/sync-basic.js +2 -2
- package/dist/esm/assertion/impl/sync-basic.js.map +1 -1
- package/dist/esm/assertion/impl/sync-collection.d.ts +1 -1
- package/dist/esm/assertion/impl/sync-collection.js +3 -3
- package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
- package/dist/esm/assertion/impl/sync-esoteric.js +2 -2
- package/dist/esm/assertion/impl/sync-esoteric.js.map +1 -1
- package/dist/esm/assertion/impl/sync-parametric.d.ts +22 -28
- package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
- package/dist/esm/assertion/impl/sync-parametric.js +36 -51
- package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
- package/dist/esm/assertion/impl/sync.d.ts +68 -30
- package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
- package/dist/esm/assertion/impl/sync.js +3 -1
- package/dist/esm/assertion/impl/sync.js.map +1 -1
- package/dist/esm/bootstrap.d.ts +147 -52
- package/dist/esm/bootstrap.d.ts.map +1 -1
- package/dist/esm/bootstrap.js +1 -2
- package/dist/esm/bootstrap.js.map +1 -1
- package/dist/esm/constant.d.ts +1 -1
- package/dist/esm/constant.d.ts.map +1 -1
- package/dist/esm/constant.js +7 -0
- package/dist/esm/constant.js.map +1 -1
- package/dist/esm/error.d.ts +22 -2
- package/dist/esm/error.d.ts.map +1 -1
- package/dist/esm/error.js +43 -4
- package/dist/esm/error.js.map +1 -1
- package/dist/esm/expect.d.ts.map +1 -1
- package/dist/esm/expect.js +2 -2
- package/dist/esm/expect.js.map +1 -1
- package/dist/esm/guards.d.ts +96 -5
- package/dist/esm/guards.d.ts.map +1 -1
- package/dist/esm/guards.js +98 -21
- package/dist/esm/guards.js.map +1 -1
- package/dist/esm/index.d.ts +146 -51
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/schema.d.ts +84 -18
- package/dist/esm/schema.d.ts.map +1 -1
- package/dist/esm/schema.js +107 -22
- package/dist/esm/schema.js.map +1 -1
- package/dist/esm/types.d.ts +171 -9
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/use.d.ts.map +1 -1
- package/dist/esm/use.js +15 -1
- package/dist/esm/use.js.map +1 -1
- package/dist/esm/util.d.ts +66 -50
- package/dist/esm/util.d.ts.map +1 -1
- package/dist/esm/util.js +153 -154
- package/dist/esm/util.js.map +1 -1
- package/dist/esm/value-to-schema.d.ts +122 -0
- package/dist/esm/value-to-schema.d.ts.map +1 -0
- package/dist/esm/value-to-schema.js +325 -0
- package/dist/esm/value-to-schema.js.map +1 -0
- package/package.json +16 -13
- package/src/assertion/assertion-async.ts +113 -3
- package/src/assertion/assertion-sync.ts +5 -2
- package/src/assertion/assertion-types.ts +14 -4
- package/src/assertion/assertion.ts +2 -17
- package/src/assertion/impl/async.ts +137 -93
- package/src/assertion/impl/callback.ts +882 -0
- package/src/assertion/impl/index.ts +1 -1
- package/src/assertion/impl/sync-basic.ts +5 -2
- package/src/assertion/impl/sync-collection.ts +3 -3
- package/src/assertion/impl/sync-esoteric.ts +2 -2
- package/src/assertion/impl/sync-parametric.ts +47 -54
- package/src/assertion/impl/sync.ts +3 -0
- package/src/bootstrap.ts +1 -2
- package/src/constant.ts +10 -0
- package/src/error.ts +57 -3
- package/src/expect.ts +6 -2
- package/src/guards.ts +125 -18
- package/src/index.ts +3 -0
- package/src/schema.ts +121 -23
- package/src/types.ts +205 -10
- package/src/use.ts +22 -0
- package/src/util.ts +168 -223
- package/src/value-to-schema.ts +489 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { isExpectItExecutor, isNonNullObject, isObject, isPromiseLike, isString, isZodType, } from './guards.js';
|
|
3
|
+
import { RegExpSchema, StrongMapSchema, StrongSetSchema, WrappedPromiseLikeSchema, } from './schema.js';
|
|
4
|
+
/**
|
|
5
|
+
* Recursively converts an arbitrary value to a Zod v4 schema that would
|
|
6
|
+
* validate values with the same structure.
|
|
7
|
+
*
|
|
8
|
+
* This function analyzes the runtime value and generates a corresponding Zod
|
|
9
|
+
* schema that captures the value's structure and type information. It handles
|
|
10
|
+
* primitives, objects, arrays, functions, and various built-in types, with
|
|
11
|
+
* support for circular reference detection.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Primitive types
|
|
17
|
+
* valueToSchema('hello'); // z.string()
|
|
18
|
+
* valueToSchema(42); // z.number()
|
|
19
|
+
* valueToSchema(true); // z.boolean()
|
|
20
|
+
*
|
|
21
|
+
* // Objects
|
|
22
|
+
* valueToSchema({ name: 'John', age: 30 });
|
|
23
|
+
* // z.object({ name: z.string(), age: z.number() })
|
|
24
|
+
*
|
|
25
|
+
* // Arrays
|
|
26
|
+
* valueToSchema(['a', 'b', 'c']); // z.array(z.string())
|
|
27
|
+
* valueToSchema([1, 'mixed']); // z.array(z.union([z.number(), z.string()]))
|
|
28
|
+
*
|
|
29
|
+
* // Nested structures
|
|
30
|
+
* valueToSchema({ users: [{ name: 'John' }] });
|
|
31
|
+
* // z.object({ users: z.array(z.object({ name: z.string() })) })
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @param value - The value to convert to a schema
|
|
35
|
+
* @param options - Configuration options for schema generation
|
|
36
|
+
* @param visited - Internal WeakSet for circular reference detection
|
|
37
|
+
* @returns A Zod schema that validates values matching the input's structure.
|
|
38
|
+
* This value is unfortunately untyped due to the complexity involved. But the
|
|
39
|
+
* schema works!
|
|
40
|
+
*/
|
|
41
|
+
export const valueToSchema = (value, options = {}, visited = new WeakSet()) => {
|
|
42
|
+
const { _currentDepth = 0, literalEmptyObjects = false, literalPrimitives = false, literalRegExp = false, literalTuples = false, maxDepth = 10, noMixedArrays = false, strict = false, } = options;
|
|
43
|
+
// Prevent infinite recursion
|
|
44
|
+
if (_currentDepth >= maxDepth) {
|
|
45
|
+
return z.unknown();
|
|
46
|
+
}
|
|
47
|
+
// Handle primitives
|
|
48
|
+
if (value === null) {
|
|
49
|
+
return z.null();
|
|
50
|
+
}
|
|
51
|
+
if (value === undefined) {
|
|
52
|
+
return literalPrimitives
|
|
53
|
+
? z.custom((val) => val === undefined, {
|
|
54
|
+
message: 'Expected undefined',
|
|
55
|
+
})
|
|
56
|
+
: z.undefined();
|
|
57
|
+
}
|
|
58
|
+
if (Number.isNaN(value)) {
|
|
59
|
+
return z.nan();
|
|
60
|
+
}
|
|
61
|
+
if (value === Infinity || value === -Infinity) {
|
|
62
|
+
return z.literal(value);
|
|
63
|
+
}
|
|
64
|
+
const valueType = typeof value;
|
|
65
|
+
switch (valueType) {
|
|
66
|
+
case 'bigint':
|
|
67
|
+
return literalPrimitives ? z.literal(value) : z.bigint();
|
|
68
|
+
case 'boolean':
|
|
69
|
+
return literalPrimitives ? z.literal(value) : z.boolean();
|
|
70
|
+
case 'function':
|
|
71
|
+
// Check if this is an ExpectItExecutor
|
|
72
|
+
if (isExpectItExecutor(value)) {
|
|
73
|
+
// Only allow nested assertions when strict is false (e.g., "to satisfy" semantics)
|
|
74
|
+
if (strict) {
|
|
75
|
+
throw new TypeError('ExpectItExecutor (expect.it) functions are not allowed in strict mode. ' +
|
|
76
|
+
'Use "to satisfy" assertions for nested expectations.');
|
|
77
|
+
}
|
|
78
|
+
// Return a schema that executes the ExpectItExecutor when validated
|
|
79
|
+
return z.custom((subject) => {
|
|
80
|
+
try {
|
|
81
|
+
value(subject);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}, {
|
|
88
|
+
message: 'Failed expect.it assertion',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return z.function();
|
|
92
|
+
case 'number':
|
|
93
|
+
return literalPrimitives ? z.literal(value) : z.number();
|
|
94
|
+
case 'string':
|
|
95
|
+
return literalPrimitives ? z.literal(value) : z.string();
|
|
96
|
+
case 'symbol':
|
|
97
|
+
return z.symbol();
|
|
98
|
+
}
|
|
99
|
+
// Handle objects
|
|
100
|
+
if (typeof value === 'object' && value !== null) {
|
|
101
|
+
// Check for circular references
|
|
102
|
+
if (visited.has(value)) {
|
|
103
|
+
// Return a recursive schema reference or unknown for circular refs
|
|
104
|
+
return z.unknown();
|
|
105
|
+
}
|
|
106
|
+
visited.add(value);
|
|
107
|
+
try {
|
|
108
|
+
// Check for objects with own __proto__ property - these can cause unexpected behavior
|
|
109
|
+
if (Object.hasOwn(value, '__proto__')) {
|
|
110
|
+
throw new TypeError('Objects with an own "__proto__" property are not supported by valueToSchema');
|
|
111
|
+
}
|
|
112
|
+
// Handle built-in object types
|
|
113
|
+
if (value instanceof Date) {
|
|
114
|
+
// Check if it's a valid date
|
|
115
|
+
if (isNaN(value.getTime())) {
|
|
116
|
+
// For invalid dates, use a literal or custom validator
|
|
117
|
+
return z.custom((val) => val instanceof Date && isNaN(val.getTime()), {
|
|
118
|
+
message: 'Expected an invalid Date',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return z.date();
|
|
122
|
+
}
|
|
123
|
+
if (value instanceof RegExp) {
|
|
124
|
+
if (literalRegExp) {
|
|
125
|
+
return RegExpSchema;
|
|
126
|
+
}
|
|
127
|
+
return z.coerce.string().regex(value);
|
|
128
|
+
}
|
|
129
|
+
if (value instanceof Map) {
|
|
130
|
+
return StrongMapSchema;
|
|
131
|
+
}
|
|
132
|
+
if (value instanceof Set) {
|
|
133
|
+
return StrongSetSchema;
|
|
134
|
+
}
|
|
135
|
+
if (value instanceof WeakMap) {
|
|
136
|
+
return z.instanceof(WeakMap);
|
|
137
|
+
}
|
|
138
|
+
if (value instanceof WeakSet) {
|
|
139
|
+
return z.instanceof(WeakSet);
|
|
140
|
+
}
|
|
141
|
+
if (value instanceof Error) {
|
|
142
|
+
return z.instanceof(Error);
|
|
143
|
+
}
|
|
144
|
+
if (isPromiseLike(value)) {
|
|
145
|
+
return WrappedPromiseLikeSchema;
|
|
146
|
+
}
|
|
147
|
+
// Handle arrays
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
// For arrays, we need to preserve undefined values while allowing
|
|
150
|
+
// other elements to use the original literalPrimitives setting
|
|
151
|
+
const filteredValue = value; // Always process all elements
|
|
152
|
+
if (filteredValue.length === 0) {
|
|
153
|
+
// For empty arrays, use z.tuple() if literalTuples is enabled
|
|
154
|
+
if (literalTuples) {
|
|
155
|
+
return z.tuple([]);
|
|
156
|
+
}
|
|
157
|
+
return z.array(z.never());
|
|
158
|
+
}
|
|
159
|
+
const elementSchemas = filteredValue.map((item) => {
|
|
160
|
+
// Use literal mode for undefined values to preserve them exactly,
|
|
161
|
+
// but use the original setting for other values
|
|
162
|
+
const itemLiteralPrimitives = item === undefined ? true : literalPrimitives;
|
|
163
|
+
return valueToSchema(item, {
|
|
164
|
+
...options,
|
|
165
|
+
_currentDepth: _currentDepth + 1,
|
|
166
|
+
literalPrimitives: itemLiteralPrimitives,
|
|
167
|
+
}, visited);
|
|
168
|
+
});
|
|
169
|
+
// Use z.tuple() if literalTuples is enabled
|
|
170
|
+
if (literalTuples) {
|
|
171
|
+
return z.tuple(elementSchemas);
|
|
172
|
+
}
|
|
173
|
+
if (!noMixedArrays) {
|
|
174
|
+
// Helper function to generate structural keys for schemas
|
|
175
|
+
const getSchemaKey = (zodType) => {
|
|
176
|
+
const schema = zodType;
|
|
177
|
+
if (isZodType(schema, 'literal')) {
|
|
178
|
+
return `${schema.constructor.name}:${String(schema.def.values)}`;
|
|
179
|
+
}
|
|
180
|
+
if (isZodType(schema, 'array')) {
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
182
|
+
const elementKey = getSchemaKey(schema.def.element);
|
|
183
|
+
return `ZodArray<${elementKey}>`;
|
|
184
|
+
}
|
|
185
|
+
if (isZodType(schema, 'object')) {
|
|
186
|
+
// For objects, create a key based on the property keys and their types
|
|
187
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
188
|
+
const shape = schema.def.shape;
|
|
189
|
+
const shapeKeys = Object.keys(shape)
|
|
190
|
+
.sort()
|
|
191
|
+
.map((key) => {
|
|
192
|
+
const propSchema = shape[key];
|
|
193
|
+
return `${key}:${getSchemaKey(propSchema)}`;
|
|
194
|
+
});
|
|
195
|
+
return `ZodObject<{${shapeKeys.join(',')}}>`;
|
|
196
|
+
}
|
|
197
|
+
if (isZodType(schema, 'union')) {
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
199
|
+
const optionKeys = schema.def.options
|
|
200
|
+
.map((option) => getSchemaKey(option))
|
|
201
|
+
.sort();
|
|
202
|
+
return `ZodUnion<[${optionKeys.join(',')}]>`;
|
|
203
|
+
}
|
|
204
|
+
// For other types, use the constructor name
|
|
205
|
+
return schema.constructor.name;
|
|
206
|
+
};
|
|
207
|
+
const seenSchemaKeys = new Set();
|
|
208
|
+
const uniqueSchemas = [];
|
|
209
|
+
for (const schema of elementSchemas) {
|
|
210
|
+
const schemaKey = getSchemaKey(schema);
|
|
211
|
+
if (!seenSchemaKeys.has(schemaKey)) {
|
|
212
|
+
seenSchemaKeys.add(schemaKey);
|
|
213
|
+
uniqueSchemas.push(schema);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (uniqueSchemas.length === 1) {
|
|
217
|
+
return z.array(uniqueSchemas[0]);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
return z.array(z.union(uniqueSchemas));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
// Use the first element's schema for all elements
|
|
225
|
+
return z.array(elementSchemas[0]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Handle plain objects
|
|
229
|
+
if (isNonNullObject(value)) {
|
|
230
|
+
const schemaShape = {};
|
|
231
|
+
const undefinedKeys = [];
|
|
232
|
+
for (const [key, val] of Object.entries(value)) {
|
|
233
|
+
if (isString(key)) {
|
|
234
|
+
// Skip undefined values unless we're in literalPrimitives mode
|
|
235
|
+
// This prevents objects with only undefined values from matching any object
|
|
236
|
+
if (val === undefined && !literalPrimitives) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (val === undefined && literalPrimitives) {
|
|
240
|
+
// Track keys that should have undefined values
|
|
241
|
+
undefinedKeys.push(key);
|
|
242
|
+
schemaShape[key] = z.undefined();
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
schemaShape[key] = valueToSchema(val, {
|
|
246
|
+
...options,
|
|
247
|
+
_currentDepth: _currentDepth + 1,
|
|
248
|
+
}, visited);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Create the base object schema
|
|
253
|
+
const baseSchema = strict
|
|
254
|
+
? z.strictObject(schemaShape)
|
|
255
|
+
: z.looseObject(schemaShape);
|
|
256
|
+
// If we have undefined keys in literalPrimitives mode, add validation to ensure they exist
|
|
257
|
+
if (undefinedKeys.length > 0 && literalPrimitives) {
|
|
258
|
+
return baseSchema.superRefine((data, ctx) => {
|
|
259
|
+
if (typeof data !== 'object' || data === null) {
|
|
260
|
+
ctx.addIssue({
|
|
261
|
+
code: z.ZodIssueCode.custom,
|
|
262
|
+
message: 'Expected an object',
|
|
263
|
+
});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const obj = data;
|
|
267
|
+
for (const key of undefinedKeys) {
|
|
268
|
+
if (!Object.hasOwn(obj, key)) {
|
|
269
|
+
ctx.addIssue({
|
|
270
|
+
code: z.ZodIssueCode.custom,
|
|
271
|
+
message: `Expected property "${key}" to exist with value undefined`,
|
|
272
|
+
path: [key],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// Check if this is an empty object and literalEmptyObjects is enabled
|
|
279
|
+
if (Object.keys(schemaShape).length === 0 && literalEmptyObjects) {
|
|
280
|
+
// Create a schema that only matches empty objects
|
|
281
|
+
return z.custom((val) => isObject(val) && Object.keys(val).length === 0, {
|
|
282
|
+
message: 'Expected an empty object with no own properties',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return baseSchema;
|
|
286
|
+
}
|
|
287
|
+
// Handle other object types (ArrayBuffer, etc.)
|
|
288
|
+
return z.custom((val) => typeof val === 'object' && val !== null, { message: 'Expected an object' });
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
visited.delete(value);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Fallback for unknown types
|
|
295
|
+
return z.unknown();
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Predefined options for {@link valueToSchema} optimized for object satisfaction
|
|
299
|
+
* checks.
|
|
300
|
+
*
|
|
301
|
+
* Uses literal primitives and tuples for exact matching while allowing extra
|
|
302
|
+
* properties.
|
|
303
|
+
*/
|
|
304
|
+
export const valueToSchemaOptionsForSatisfies = Object.freeze({
|
|
305
|
+
literalEmptyObjects: true,
|
|
306
|
+
literalPrimitives: true,
|
|
307
|
+
literalRegExp: false,
|
|
308
|
+
literalTuples: true,
|
|
309
|
+
strict: false,
|
|
310
|
+
});
|
|
311
|
+
/**
|
|
312
|
+
* Predefined options for {@link valueToSchema} optimized for deep equality
|
|
313
|
+
* checks.
|
|
314
|
+
*
|
|
315
|
+
* Uses literal primitives, regexp, and tuples with strict validation for exact
|
|
316
|
+
* matching.
|
|
317
|
+
*/
|
|
318
|
+
export const valueToSchemaOptionsForDeepEqual = Object.freeze({
|
|
319
|
+
literalEmptyObjects: true,
|
|
320
|
+
literalPrimitives: true,
|
|
321
|
+
literalRegExp: true,
|
|
322
|
+
literalTuples: true,
|
|
323
|
+
strict: true,
|
|
324
|
+
});
|
|
325
|
+
//# sourceMappingURL=value-to-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-to-schema.js","sourceRoot":"","sources":["../../src/value-to-schema.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bupkis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Uncommonly extensible assertions for the beautiful people",
|
|
6
6
|
"repository": {
|
|
@@ -111,24 +111,26 @@
|
|
|
111
111
|
"build:docs": "typedoc --cleanOutputDir --treatWarningsAsErrors",
|
|
112
112
|
"build:docs:dev": "run-s \"build:docs -- --watch\"",
|
|
113
113
|
"docs:dev": "run-p build:docs:dev serve",
|
|
114
|
-
"lint": "run-p lint:eslint lint:types lint:knip lint:markdown",
|
|
114
|
+
"lint": "run-p lint:eslint lint:types lint:knip lint:markdown lint:spelling",
|
|
115
115
|
"lint:commit": "commitlint",
|
|
116
116
|
"lint:eslint": "eslint .",
|
|
117
117
|
"lint:fix": "run-s \"lint:eslint -- --fix\"",
|
|
118
118
|
"lint:knip": "knip",
|
|
119
119
|
"lint:markdown": "markdownlint-cli2 \"**/*.md\"",
|
|
120
|
+
"lint:spelling": "cspell \"**\"",
|
|
120
121
|
"lint:staged": "lint-staged",
|
|
121
122
|
"lint:types": "tsc -b .config/tsconfig.eslint.json",
|
|
122
123
|
"lint:types:dev": "run-s \"lint:types -- --watch\"",
|
|
123
124
|
"prepare": "husky; run-s build",
|
|
124
125
|
"prepublishOnly": "run-s build",
|
|
125
126
|
"serve": "serve docs",
|
|
126
|
-
"test": "
|
|
127
|
+
"test": "run-s \"test:base -- test/**/*.test.ts\"",
|
|
128
|
+
"test:base": "node --test --test-reporter=spec --import tsx",
|
|
127
129
|
"test:coverage": "c8 --reporter=lcov --reporter=text npm test",
|
|
128
|
-
"test:dev": "node
|
|
129
|
-
"test:profile": "node --cpu-prof --cpu-prof-dir=.profiles
|
|
130
|
-
"test:property": "node
|
|
131
|
-
"test:property:dev": "node
|
|
130
|
+
"test:dev": "node --test --import tsx --watch --test-reporter=spec \"test/**/*.test.ts\"",
|
|
131
|
+
"test:profile": "node --cpu-prof --cpu-prof-dir=.profiles --test --import tsx --test-reporter=spec \"test/**/*.test.ts\"",
|
|
132
|
+
"test:property": "node --test --import tsx --test-reporter=spec \"test/property/*.test.ts\"",
|
|
133
|
+
"test:property:dev": "node --test --import tsx --test-reporter=spec --watch \"test/property/*.test.ts\""
|
|
132
134
|
},
|
|
133
135
|
"peerDependencies": {
|
|
134
136
|
"zod": "^4.1.5"
|
|
@@ -149,6 +151,7 @@
|
|
|
149
151
|
"@types/slug": "5.0.9",
|
|
150
152
|
"@types/wallabyjs": "0.0.15",
|
|
151
153
|
"c8": "10.1.3",
|
|
154
|
+
"cspell": "9.2.1",
|
|
152
155
|
"eslint": "9.35.0",
|
|
153
156
|
"eslint-plugin-jsonc": "2.20.1",
|
|
154
157
|
"eslint-plugin-perfectionist": "4.15.0",
|
|
@@ -176,7 +179,7 @@
|
|
|
176
179
|
"typedoc-plugin-redirect": "1.2.0",
|
|
177
180
|
"typedoc-plugin-zod": "1.4.2",
|
|
178
181
|
"typescript": "5.9.2",
|
|
179
|
-
"typescript-eslint": "8.
|
|
182
|
+
"typescript-eslint": "8.43.0"
|
|
180
183
|
},
|
|
181
184
|
"publishConfig": {
|
|
182
185
|
"access": "public",
|
|
@@ -185,9 +188,7 @@
|
|
|
185
188
|
"knip": {
|
|
186
189
|
"ignoreDependencies": [
|
|
187
190
|
"@types/wallabyjs",
|
|
188
|
-
"markdownlint-cli2-formatter-pretty"
|
|
189
|
-
"expect-type",
|
|
190
|
-
"tsx"
|
|
191
|
+
"markdownlint-cli2-formatter-pretty"
|
|
191
192
|
],
|
|
192
193
|
"ignore": [
|
|
193
194
|
".wallaby.js",
|
|
@@ -215,10 +216,12 @@
|
|
|
215
216
|
"lint-staged": {
|
|
216
217
|
"*.{ts,cts,js,json,yml,json5}": [
|
|
217
218
|
"eslint --fix",
|
|
218
|
-
"prettier --write"
|
|
219
|
+
"prettier --write",
|
|
220
|
+
"cspell"
|
|
219
221
|
],
|
|
220
222
|
"**/!(.github/prompts)/*.md": [
|
|
221
|
-
"prettier --write"
|
|
223
|
+
"prettier --write",
|
|
224
|
+
"cspell"
|
|
222
225
|
]
|
|
223
226
|
},
|
|
224
227
|
"prettier": {
|
|
@@ -4,7 +4,13 @@ import z from 'zod/v4';
|
|
|
4
4
|
|
|
5
5
|
import { kStringLiteral } from '../constant.js';
|
|
6
6
|
import { AssertionError } from '../error.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
isA,
|
|
9
|
+
isAssertionFailure,
|
|
10
|
+
isBoolean,
|
|
11
|
+
isError,
|
|
12
|
+
isZodType,
|
|
13
|
+
} from '../guards.js';
|
|
8
14
|
import { BupkisRegistry } from '../metadata.js';
|
|
9
15
|
import {
|
|
10
16
|
type AssertionAsync,
|
|
@@ -16,6 +22,7 @@ import {
|
|
|
16
22
|
type AssertionSchemaAsync,
|
|
17
23
|
type AssertionSlots,
|
|
18
24
|
type ParsedResult,
|
|
25
|
+
type ParsedResultSuccess,
|
|
19
26
|
type ParsedValues,
|
|
20
27
|
} from './assertion-types.js';
|
|
21
28
|
import { BupkisAssertion } from './assertion.js';
|
|
@@ -133,6 +140,8 @@ export class BupkisAssertionFunctionAsync<
|
|
|
133
140
|
expected: result.expected,
|
|
134
141
|
message: result.message ?? `Assertion ${this} failed`,
|
|
135
142
|
});
|
|
143
|
+
} else if (isError(result) && result instanceof z.ZodError) {
|
|
144
|
+
throw this.translateZodError(stackStartFn, result, ...parsedValues);
|
|
136
145
|
} else if (result as unknown) {
|
|
137
146
|
throw new TypeError(
|
|
138
147
|
`Invalid return type from assertion ${this}; expected boolean, ZodType, or AssertionFailure`,
|
|
@@ -166,9 +175,31 @@ export class BupkisAssertionSchemaAsync<
|
|
|
166
175
|
parsedValues: ParsedValues<Parts>,
|
|
167
176
|
_args: unknown[],
|
|
168
177
|
stackStartFn: (...args: any[]) => any,
|
|
169
|
-
|
|
178
|
+
parseResult?: ParsedResult<Parts>,
|
|
170
179
|
): Promise<void> {
|
|
171
|
-
//
|
|
180
|
+
// Check if we have cached validation result from parseValuesAsync
|
|
181
|
+
const cachedValidation = parseResult?.success
|
|
182
|
+
? parseResult.subjectValidationResult
|
|
183
|
+
: undefined;
|
|
184
|
+
|
|
185
|
+
if (cachedValidation) {
|
|
186
|
+
debug(
|
|
187
|
+
'Using cached subject validation result from parseValuesAsync for %s',
|
|
188
|
+
this,
|
|
189
|
+
);
|
|
190
|
+
if (!cachedValidation.success) {
|
|
191
|
+
// Subject validation failed during parseValuesAsync, throw the cached error
|
|
192
|
+
throw this.translateZodError(
|
|
193
|
+
stackStartFn,
|
|
194
|
+
cachedValidation.error,
|
|
195
|
+
...parsedValues,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
// Subject validation passed, nothing more to do
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Fall back to standard validation if no cached result
|
|
172
203
|
const [subject] = parsedValues;
|
|
173
204
|
try {
|
|
174
205
|
await this.impl.parseAsync(subject);
|
|
@@ -180,6 +211,85 @@ export class BupkisAssertionSchemaAsync<
|
|
|
180
211
|
}
|
|
181
212
|
}
|
|
182
213
|
|
|
214
|
+
override async parseValuesAsync<Args extends readonly unknown[]>(
|
|
215
|
+
args: Args,
|
|
216
|
+
): Promise<ParsedResult<Parts>> {
|
|
217
|
+
const { slots } = this;
|
|
218
|
+
const parsedValues: any[] = [];
|
|
219
|
+
const mismatch = this.maybeParseValuesArgMismatch(args);
|
|
220
|
+
if (mismatch) {
|
|
221
|
+
return mismatch;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let exactMatch = true;
|
|
225
|
+
let subjectValidationResult:
|
|
226
|
+
| undefined
|
|
227
|
+
| { data: any; success: true }
|
|
228
|
+
| { error: z.ZodError; success: false };
|
|
229
|
+
|
|
230
|
+
for (let i = 0; i < slots.length; i++) {
|
|
231
|
+
const slot = slots[i]!;
|
|
232
|
+
const arg = args[i];
|
|
233
|
+
const parsedLiteralResult = this.parseSlotForLiteral(slot, i, arg);
|
|
234
|
+
if (parsedLiteralResult === true) {
|
|
235
|
+
continue;
|
|
236
|
+
} else if (parsedLiteralResult !== false) {
|
|
237
|
+
return parsedLiteralResult;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// For the subject slot (first slot if it's unknown/any), try optimized validation
|
|
241
|
+
if (
|
|
242
|
+
i === 0 &&
|
|
243
|
+
(slot.def.type === 'unknown' || slot.def.type === 'any') &&
|
|
244
|
+
this.isSimpleSchemaAssertion()
|
|
245
|
+
) {
|
|
246
|
+
try {
|
|
247
|
+
const result = await this.impl.parseAsync(arg);
|
|
248
|
+
subjectValidationResult = { data: result, success: true };
|
|
249
|
+
parsedValues.push(result); // Use validated data
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (isA(error, z.ZodError)) {
|
|
252
|
+
subjectValidationResult = { error, success: false };
|
|
253
|
+
parsedValues.push(arg); // Keep original for error reporting
|
|
254
|
+
} else {
|
|
255
|
+
throw error; // Re-throw non-Zod errors
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
exactMatch = false; // Subject was validated, so we know the exact type
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Standard slot processing for non-optimized cases
|
|
263
|
+
if (slot.def.type === 'unknown' || slot.def.type === 'any') {
|
|
264
|
+
debug('Skipping unknown/any slot validation for arg', arg);
|
|
265
|
+
parsedValues.push(arg);
|
|
266
|
+
exactMatch = false;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const result = await slot.safeParseAsync(arg);
|
|
271
|
+
if (!result.success) {
|
|
272
|
+
return {
|
|
273
|
+
success: false,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
parsedValues.push(result.data);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const result: ParsedResultSuccess<Parts> = {
|
|
280
|
+
exactMatch,
|
|
281
|
+
parsedValues: parsedValues as unknown as ParsedValues<Parts>,
|
|
282
|
+
success: true,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Add cached validation result if we performed optimization
|
|
286
|
+
if (subjectValidationResult) {
|
|
287
|
+
result.subjectValidationResult = subjectValidationResult;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
183
293
|
/**
|
|
184
294
|
* Determines if this assertion can be optimized (simple single-subject
|
|
185
295
|
* schema). Only simple assertions like ['to be a string'] with z.string()
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
import Debug from 'debug';
|
|
10
10
|
import { inspect } from 'util';
|
|
11
|
-
import {
|
|
11
|
+
import { z } from 'zod/v4';
|
|
12
12
|
|
|
13
13
|
import { kStringLiteral } from '../constant.js';
|
|
14
14
|
import { AssertionError } from '../error.js';
|
|
15
15
|
import {
|
|
16
16
|
isAssertionFailure,
|
|
17
17
|
isBoolean,
|
|
18
|
+
isError,
|
|
18
19
|
isPromiseLike,
|
|
19
20
|
isZodPromise,
|
|
20
21
|
isZodType,
|
|
@@ -183,6 +184,8 @@ export class BupkisAssertionFunctionSync<
|
|
|
183
184
|
expected: result.expected,
|
|
184
185
|
message: result.message ?? `Assertion ${this} failed`,
|
|
185
186
|
});
|
|
187
|
+
} else if (isError(result) && result instanceof z.ZodError) {
|
|
188
|
+
throw this.translateZodError(stackStartFn, result, ...parsedValues);
|
|
186
189
|
} else if (result as unknown) {
|
|
187
190
|
throw new TypeError(
|
|
188
191
|
`Invalid return type from assertion ${this}; expected boolean, ZodType, or AssertionFailure`,
|
|
@@ -214,7 +217,7 @@ export class BupkisAssertionSchemaSync<
|
|
|
214
217
|
{
|
|
215
218
|
override execute(
|
|
216
219
|
parsedValues: ParsedValues<Parts>,
|
|
217
|
-
|
|
220
|
+
_args: unknown[],
|
|
218
221
|
stackStartFn: (...args: any[]) => any,
|
|
219
222
|
parseResult?: ParsedResult<Parts>,
|
|
220
223
|
): void {
|
|
@@ -227,9 +227,19 @@ export type AssertionImplAsync<Parts extends AssertionParts> =
|
|
|
227
227
|
*/
|
|
228
228
|
export type AssertionImplFnAsync<Parts extends AssertionParts> = (
|
|
229
229
|
...values: ParsedValues<Parts>
|
|
230
|
-
) =>
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
) =>
|
|
231
|
+
| AssertionImplFnReturnType<Parts>
|
|
232
|
+
| Promise<AssertionImplFnReturnType<Parts>>;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The return type of an assertion implementation function.
|
|
236
|
+
*/
|
|
237
|
+
export type AssertionImplFnReturnType<Parts extends AssertionParts> =
|
|
238
|
+
| AssertionFailure
|
|
239
|
+
| boolean
|
|
240
|
+
| void
|
|
241
|
+
| z.ZodError
|
|
242
|
+
| z.ZodType<ParsedSubject<Parts>>;
|
|
233
243
|
|
|
234
244
|
/**
|
|
235
245
|
* The implementation of an assertion as a sync function.
|
|
@@ -248,7 +258,7 @@ export type AssertionImplFnAsync<Parts extends AssertionParts> = (
|
|
|
248
258
|
*/
|
|
249
259
|
export type AssertionImplFnSync<Parts extends AssertionParts> = (
|
|
250
260
|
...values: ParsedValues<Parts>
|
|
251
|
-
) =>
|
|
261
|
+
) => AssertionImplFnReturnType<Parts>;
|
|
252
262
|
|
|
253
263
|
/**
|
|
254
264
|
* Maps an {@link AssertionPart} to a parameter to an {@link AssertionImpl}.
|
|
@@ -13,7 +13,7 @@ import Debug from 'debug';
|
|
|
13
13
|
import slug from 'slug';
|
|
14
14
|
import { type ArrayValues } from 'type-fest';
|
|
15
15
|
import { inspect } from 'util';
|
|
16
|
-
import { z } from 'zod/v4';
|
|
16
|
+
import { type z } from 'zod/v4';
|
|
17
17
|
|
|
18
18
|
import { kStringLiteral } from '../constant.js';
|
|
19
19
|
import { AssertionError } from '../error.js';
|
|
@@ -186,22 +186,7 @@ export abstract class BupkisAssertion<
|
|
|
186
186
|
zodError: z.ZodError,
|
|
187
187
|
...values: ParsedValues<Parts>
|
|
188
188
|
): AssertionError {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
let pretty = flat.formErrors.join('; ');
|
|
192
|
-
for (const [keypath, errors] of Object.entries(flat.fieldErrors)) {
|
|
193
|
-
pretty += `; ${keypath}: ${(errors as unknown[]).join('; ')}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const [actual, ...expected] = values as unknown as [unknown, ...unknown[]];
|
|
197
|
-
|
|
198
|
-
return new AssertionError({
|
|
199
|
-
actual,
|
|
200
|
-
expected: expected.length === 1 ? expected[0] : expected,
|
|
201
|
-
message: `Assertion ${this} failed: ${pretty}`,
|
|
202
|
-
operator: `${this}`,
|
|
203
|
-
stackStartFn,
|
|
204
|
-
});
|
|
189
|
+
return AssertionError.fromZodError(zodError, stackStartFn, values);
|
|
205
190
|
}
|
|
206
191
|
|
|
207
192
|
/**
|