bupkis 0.0.2
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 +20 -0
- package/LICENSE.md +55 -0
- package/README.md +170 -0
- package/package.json +164 -0
- package/src/api.ts +149 -0
- package/src/assertion/assertion-async.ts +203 -0
- package/src/assertion/assertion-sync.ts +351 -0
- package/src/assertion/assertion-types.ts +964 -0
- package/src/assertion/assertion.ts +234 -0
- package/src/assertion/create.ts +226 -0
- package/src/assertion/impl/README.md +13 -0
- package/src/assertion/impl/async.ts +284 -0
- package/src/assertion/impl/index.ts +2 -0
- package/src/assertion/impl/sync-basic.ts +111 -0
- package/src/assertion/impl/sync-collection.ts +108 -0
- package/src/assertion/impl/sync-esoteric.ts +25 -0
- package/src/assertion/impl/sync-parametric.ts +488 -0
- package/src/assertion/impl/sync.ts +29 -0
- package/src/assertion/index.ts +15 -0
- package/src/assertion/slotify.ts +89 -0
- package/src/bootstrap.ts +55 -0
- package/src/constant.ts +37 -0
- package/src/error.ts +47 -0
- package/src/expect.ts +364 -0
- package/src/guards.ts +223 -0
- package/src/index.ts +29 -0
- package/src/metadata.ts +60 -0
- package/src/schema.md +15 -0
- package/src/schema.ts +464 -0
- package/src/types.ts +159 -0
- package/src/use.ts +63 -0
- package/src/util.ts +264 -0
package/src/metadata.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines Bupkis' Zod metadata registry
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
import { kStringLiteral } from './constant.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Metadata stored in Zod registry
|
|
13
|
+
*
|
|
14
|
+
* @knipignore
|
|
15
|
+
*/
|
|
16
|
+
export type BupkisMeta = z.infer<typeof BupkisRegistrySchema>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Zod metadata registry for Bupkis
|
|
20
|
+
*/
|
|
21
|
+
export const BupkisRegistry = z.registry<BupkisMeta>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base schema for all metadata
|
|
25
|
+
*/
|
|
26
|
+
const BaseBupkisMetadataSchema = z.object({
|
|
27
|
+
description: z.string().optional().describe('Human-friendly description'),
|
|
28
|
+
name: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('Internal name; used by Assertion.prototype.toString()'),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Base schema for metadata referring to string literal flag
|
|
36
|
+
*/
|
|
37
|
+
const StringLiteralFlagSchema = z.object({
|
|
38
|
+
...BaseBupkisMetadataSchema.shape,
|
|
39
|
+
[kStringLiteral]: z.literal(true),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Final schema for Bupkis registry
|
|
44
|
+
*/
|
|
45
|
+
// TODO: Figure out how to make this rule allow type-only usage. Or just export it and tag it.
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
47
|
+
const BupkisRegistrySchema = z.union([
|
|
48
|
+
z.object({ ...BaseBupkisMetadataSchema.shape }),
|
|
49
|
+
z.object({
|
|
50
|
+
...StringLiteralFlagSchema.shape,
|
|
51
|
+
value: z.string(),
|
|
52
|
+
values: z.never().optional(),
|
|
53
|
+
}),
|
|
54
|
+
z.object({
|
|
55
|
+
...StringLiteralFlagSchema.shape,
|
|
56
|
+
value: z.never().optional(),
|
|
57
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
58
|
+
values: z.tuple([z.string()]).rest(z.string()).readonly(),
|
|
59
|
+
}),
|
|
60
|
+
]);
|
package/src/schema.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: About Schemas
|
|
3
|
+
group: Documents
|
|
4
|
+
category: 'For Developers'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# About Schemas
|
|
8
|
+
|
|
9
|
+
The schemas located in [schema.ts](./schema.ts) are used internally, but will be helpful for anyone implementing custom assertions.
|
|
10
|
+
|
|
11
|
+
These contain workarounds for where Zod's concepts and scope conflict with the aim of _BUPKIS_.
|
|
12
|
+
|
|
13
|
+
For example, we have a `FunctionSchema` which accepts any function, regardless of its signature. Zod v4's `z.function()` is no longer a `ZodType` and acts differently, but this schema allows us to validate "is a function" assertions and perform assertion matching.
|
|
14
|
+
|
|
15
|
+
Likewise, `z.promise()` does not parse a `Promise` instance, but rather the resolved value of a promise. This is not what we want for "is a Promise" assertions, so we have `PromiseSchema` to handle that.
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema definitions for common types and validation patterns.
|
|
3
|
+
*
|
|
4
|
+
* This module provides reusable Zod schemas for validating constructors,
|
|
5
|
+
* functions, property keys, promises, and other common JavaScript types used
|
|
6
|
+
* throughout the assertion system. These tend to work around Zod's
|
|
7
|
+
* limitations.
|
|
8
|
+
*
|
|
9
|
+
* These are used internally, but consumers may also find them useful.
|
|
10
|
+
*
|
|
11
|
+
* @document schema.md
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { z } from 'zod/v4';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
isA,
|
|
19
|
+
isConstructable,
|
|
20
|
+
isFunction,
|
|
21
|
+
isNonNullObject,
|
|
22
|
+
isPromiseLike,
|
|
23
|
+
} from './guards.js';
|
|
24
|
+
import { BupkisRegistry } from './metadata.js';
|
|
25
|
+
import { type Constructor } from './types.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A Zod schema that validates JavaScript classes or constructor functions.
|
|
29
|
+
*
|
|
30
|
+
* This schema validates values that can be used as constructors, including ES6
|
|
31
|
+
* classes, traditional constructor functions, and built-in constructors. It
|
|
32
|
+
* uses the {@link isConstructable} guard function to determine if a value can be
|
|
33
|
+
* invoked with the `new` operator to create object instances.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* The schema is registered in the {@link BupkisRegistry} with the name
|
|
37
|
+
* `ClassSchema` for later reference and type checking purposes.
|
|
38
|
+
* @category Schema
|
|
39
|
+
* @example
|
|
40
|
+
*
|
|
41
|
+
* ```typescript
|
|
42
|
+
* class MyClass {}
|
|
43
|
+
* function MyConstructor() {}
|
|
44
|
+
*
|
|
45
|
+
* ClassSchema.parse(MyClass); // ✓ Valid
|
|
46
|
+
* ClassSchema.parse(MyConstructor); // ✓ Valid
|
|
47
|
+
* ClassSchema.parse(Array); // ✓ Valid
|
|
48
|
+
* ClassSchema.parse(Date); // ✓ Valid
|
|
49
|
+
* ClassSchema.parse(() => {}); // ✗ Throws validation error
|
|
50
|
+
* ClassSchema.parse({}); // ✗ Throws validation error
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
export const ClassSchema = z
|
|
55
|
+
.custom<Constructor>(isConstructable)
|
|
56
|
+
.register(BupkisRegistry, { name: 'ClassSchema' })
|
|
57
|
+
.describe('Class / Constructor');
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A Zod schema that validates any JavaScript function.
|
|
61
|
+
*
|
|
62
|
+
* This schema provides function validation capabilities similar to the
|
|
63
|
+
* parseable-only `z.function()` from Zod v3.x, but works with Zod v4's
|
|
64
|
+
* architecture. It validates that the input value is any callable function,
|
|
65
|
+
* including regular functions, arrow functions, async functions, generator
|
|
66
|
+
* functions, and methods.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* The schema is registered in the {@link BupkisRegistry} with the name
|
|
70
|
+
* `FunctionSchema` for later reference and type checking purposes.
|
|
71
|
+
* @category Schema
|
|
72
|
+
* @example
|
|
73
|
+
*
|
|
74
|
+
* ```typescript
|
|
75
|
+
* FunctionSchema.parse(function () {}); // ✓ Valid
|
|
76
|
+
* FunctionSchema.parse(() => {}); // ✓ Valid
|
|
77
|
+
* FunctionSchema.parse(async () => {}); // ✓ Valid
|
|
78
|
+
* FunctionSchema.parse(function* () {}); // ✓ Valid
|
|
79
|
+
* FunctionSchema.parse(Math.max); // ✓ Valid
|
|
80
|
+
* FunctionSchema.parse('not a function'); // ✗ Throws validation error
|
|
81
|
+
* FunctionSchema.parse({}); // ✗ Throws validation error
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const FunctionSchema = z
|
|
85
|
+
.custom<(...args: any[]) => any>(isFunction)
|
|
86
|
+
.register(BupkisRegistry, {
|
|
87
|
+
name: 'FunctionSchema',
|
|
88
|
+
})
|
|
89
|
+
.describe(
|
|
90
|
+
'Any function; similar to parseable-only `z.function()` in Zod v3.x',
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A Zod schema that validates JavaScript property keys.
|
|
95
|
+
*
|
|
96
|
+
* This schema validates values that can be used as object property keys in
|
|
97
|
+
* JavaScript, which includes strings, numbers, and symbols. These are the three
|
|
98
|
+
* types that JavaScript automatically converts to property keys when used in
|
|
99
|
+
* object access or assignment operations.
|
|
100
|
+
*
|
|
101
|
+
* @remarks
|
|
102
|
+
* The schema is registered in the `BupkisRegistry` with the name
|
|
103
|
+
* `PropertyKeySchema` for later reference and type checking purposes.
|
|
104
|
+
* @category Schema
|
|
105
|
+
* @example
|
|
106
|
+
*
|
|
107
|
+
* ```typescript
|
|
108
|
+
* PropertyKeySchema.parse('stringKey'); // ✓ Valid
|
|
109
|
+
* PropertyKeySchema.parse(42); // ✓ Valid
|
|
110
|
+
* PropertyKeySchema.parse(Symbol('symbolKey')); // ✓ Valid
|
|
111
|
+
* PropertyKeySchema.parse({}); // ✗ Throws validation error
|
|
112
|
+
* PropertyKeySchema.parse(null); // ✗ Throws validation error
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export const PropertyKeySchema = z
|
|
116
|
+
.union([z.string(), z.number(), z.symbol()])
|
|
117
|
+
.describe('PropertyKey')
|
|
118
|
+
.register(BupkisRegistry, { name: 'PropertyKeySchema' });
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A Zod schema that validates "thenable" objects with a `.then()` method.
|
|
122
|
+
*
|
|
123
|
+
* This schema validates objects that implement the PromiseLike interface by
|
|
124
|
+
* having a `.then()` method, which includes Promises and other thenable
|
|
125
|
+
* objects. Unlike Zod's built-in `z.promise()`, this schema does not unwrap the
|
|
126
|
+
* resolved value, meaning the result of parsing remains a Promise or thenable
|
|
127
|
+
* object.
|
|
128
|
+
*
|
|
129
|
+
* @remarks
|
|
130
|
+
* The schema is registered in the `BupkisRegistry` with the name
|
|
131
|
+
* `WrappedPromiseLikeSchema` for later reference and type checking purposes.
|
|
132
|
+
* This is useful when you need to validate that something is thenable without
|
|
133
|
+
* automatically resolving it.
|
|
134
|
+
* @category Schema
|
|
135
|
+
* @example
|
|
136
|
+
*
|
|
137
|
+
* ```typescript
|
|
138
|
+
* WrappedPromiseLikeSchema.parse(Promise.resolve(42)); // ✓ Valid (returns Promise)
|
|
139
|
+
* WrappedPromiseLikeSchema.parse({ then: () => {} }); // ✓ Valid (thenable)
|
|
140
|
+
* WrappedPromiseLikeSchema.parse(42); // ✗ Throws validation error
|
|
141
|
+
* WrappedPromiseLikeSchema.parse({}); // ✗ Throws validation error
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export const WrappedPromiseLikeSchema = z
|
|
145
|
+
.custom<PromiseLike<unknown>>((value) => isPromiseLike(value))
|
|
146
|
+
.describe(
|
|
147
|
+
'PromiseLike; unlike z.promise(), does not unwrap the resolved value',
|
|
148
|
+
)
|
|
149
|
+
.register(BupkisRegistry, { name: 'WrappedPromiseLikeSchema' });
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A Zod schema that validates Map instances excluding WeakMap instances.
|
|
153
|
+
*
|
|
154
|
+
* This schema ensures that the validated value is a Map and specifically
|
|
155
|
+
* excludes WeakMap instances through refinement validation. This is useful when
|
|
156
|
+
* you need to ensure you're working with a regular Map that allows iteration
|
|
157
|
+
* and enumeration of keys, unlike WeakMaps which are not enumerable.
|
|
158
|
+
*
|
|
159
|
+
* @remarks
|
|
160
|
+
* The schema is registered in the `BupkisRegistry` with the name
|
|
161
|
+
* `StrongMapSchema` for later reference and type checking purposes.
|
|
162
|
+
* @category Schema
|
|
163
|
+
* @example
|
|
164
|
+
*
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const validMap = new Map([
|
|
167
|
+
* ['key1', 'value1'],
|
|
168
|
+
* ['key2', 'value2'],
|
|
169
|
+
* ]);
|
|
170
|
+
* StrongMapSchema.parse(validMap); // ✓ Valid
|
|
171
|
+
*
|
|
172
|
+
* const weakMap = new WeakMap();
|
|
173
|
+
* StrongMapSchema.parse(weakMap); // ✗ Throws validation error
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export const StrongMapSchema = z
|
|
177
|
+
.instanceof(Map)
|
|
178
|
+
.refine((value) => !isA(value, WeakMap))
|
|
179
|
+
.describe('A Map that is not a WeakMap')
|
|
180
|
+
.register(BupkisRegistry, { name: 'StrongMapSchema' });
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A Zod schema that validates Set instances excluding WeakSet instances.
|
|
184
|
+
*
|
|
185
|
+
* This schema ensures that the validated value is a Set and specifically
|
|
186
|
+
* excludes WeakSet instances through refinement validation. This is useful when
|
|
187
|
+
* you need to ensure you're working with a regular Set that allows iteration
|
|
188
|
+
* and enumeration of values, unlike WeakSets which are not enumerable.
|
|
189
|
+
*
|
|
190
|
+
* @remarks
|
|
191
|
+
* The schema is registered in the `BupkisRegistry` with the name
|
|
192
|
+
* `StrongSetSchema` for later reference and type checking purposes.
|
|
193
|
+
* @category Schema
|
|
194
|
+
* @example
|
|
195
|
+
*
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const validSet = new Set([1, 2, 3]);
|
|
198
|
+
* StrongSetSchema.parse(validSet); // ✓ Valid
|
|
199
|
+
*
|
|
200
|
+
* const weakSet = new WeakSet();
|
|
201
|
+
* StrongSetSchema.parse(weakSet); // ✗ Throws validation error
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export const StrongSetSchema = z
|
|
205
|
+
.instanceof(Set)
|
|
206
|
+
.refine((value) => !isA(value, WeakSet))
|
|
207
|
+
.describe('A Set that is not a WeakSet')
|
|
208
|
+
.register(BupkisRegistry, { name: 'StrongSetSchema' });
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* A Zod schema that validates plain objects with null prototypes.
|
|
212
|
+
*
|
|
213
|
+
* This schema validates objects that have been created with
|
|
214
|
+
* `Object.create(null)` or otherwise have their prototype set to `null`. Such
|
|
215
|
+
* objects are "plain" objects without any inherited properties or methods from
|
|
216
|
+
* `Object.prototype`, making them useful as pure data containers or
|
|
217
|
+
* dictionaries.
|
|
218
|
+
*
|
|
219
|
+
* @remarks
|
|
220
|
+
* The schema is registered in the `BupkisRegistry` with the name
|
|
221
|
+
* `ObjectWithNullPrototype` for later reference and type checking purposes.
|
|
222
|
+
* @category Schema
|
|
223
|
+
* @example
|
|
224
|
+
*
|
|
225
|
+
* ```typescript
|
|
226
|
+
* const nullProtoObj = Object.create(null);
|
|
227
|
+
* nullProtoObj.key = 'value';
|
|
228
|
+
* NullProtoObjectSchema.parse(nullProtoObj); // ✓ Valid
|
|
229
|
+
*
|
|
230
|
+
* const regularObj = { key: 'value' };
|
|
231
|
+
* NullProtoObjectSchema.parse(regularObj); // ✗ Throws validation error
|
|
232
|
+
*
|
|
233
|
+
* const emptyObj = {};
|
|
234
|
+
* NullProtoObjectSchema.parse(emptyObj); // ✗ Throws validation error
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
export const NullProtoObjectSchema = z
|
|
238
|
+
.custom<Record<PropertyKey, unknown>>(
|
|
239
|
+
(value) => isNonNullObject(value) && Object.getPrototypeOf(value) === null,
|
|
240
|
+
)
|
|
241
|
+
.describe('Object with null prototype')
|
|
242
|
+
.register(BupkisRegistry, { name: 'ObjectWithNullPrototype' });
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* A Zod schema that validates functions declared with the `async` keyword.
|
|
246
|
+
*
|
|
247
|
+
* This schema validates functions that are explicitly declared as asynchronous
|
|
248
|
+
* using the `async` keyword. It uses runtime introspection to check the
|
|
249
|
+
* function's internal `[[ToString]]` representation to distinguish async
|
|
250
|
+
* functions from regular functions that might return Promises.
|
|
251
|
+
*
|
|
252
|
+
* @remarks
|
|
253
|
+
* The schema is registered in the `BupkisRegistry` with the name
|
|
254
|
+
* `AsyncFunctionSchema` for later reference and type checking purposes. This
|
|
255
|
+
* schema cannot reliably detect functions that return Promises but are not
|
|
256
|
+
* declared with `async`, as this determination requires static analysis that is
|
|
257
|
+
* not available at runtime.
|
|
258
|
+
* @category Schema
|
|
259
|
+
* @example
|
|
260
|
+
*
|
|
261
|
+
* ```typescript
|
|
262
|
+
* async function asyncFn() {
|
|
263
|
+
* return 42;
|
|
264
|
+
* }
|
|
265
|
+
* AsyncFunctionSchema.parse(asyncFn); // ✓ Valid
|
|
266
|
+
*
|
|
267
|
+
* const asyncArrow = async () => 42;
|
|
268
|
+
* AsyncFunctionSchema.parse(asyncArrow); // ✓ Valid
|
|
269
|
+
*
|
|
270
|
+
* function syncFn() {
|
|
271
|
+
* return Promise.resolve(42);
|
|
272
|
+
* }
|
|
273
|
+
* AsyncFunctionSchema.parse(syncFn); // ✗ Throws validation error
|
|
274
|
+
*
|
|
275
|
+
* const regularFn = () => 42;
|
|
276
|
+
* AsyncFunctionSchema.parse(regularFn); // ✗ Throws validation error
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export const AsyncFunctionSchema = FunctionSchema.refine(
|
|
280
|
+
(value) => Object.prototype.toString.call(value) === '[object AsyncFunction]',
|
|
281
|
+
)
|
|
282
|
+
.describe('Function declared with the `async` keyword')
|
|
283
|
+
.register(BupkisRegistry, { name: 'AsyncFunctionSchema' });
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* A Zod schema that validates truthy JavaScript values.
|
|
287
|
+
*
|
|
288
|
+
* This schema accepts any input value but only validates successfully if the
|
|
289
|
+
* value is truthy according to JavaScript's truthiness rules. A value is truthy
|
|
290
|
+
* if it converts to `true` when evaluated in a boolean context - essentially
|
|
291
|
+
* any value that is not one of the eight falsy values.
|
|
292
|
+
*
|
|
293
|
+
* @remarks
|
|
294
|
+
* The schema is registered in the `BupkisRegistry` with the name `Truthy` and
|
|
295
|
+
* indicates that it accepts anything as valid input for evaluation.
|
|
296
|
+
* @category Schema
|
|
297
|
+
* @example
|
|
298
|
+
*
|
|
299
|
+
* ```typescript
|
|
300
|
+
* TruthySchema.parse(true); // ✓ Valid
|
|
301
|
+
* TruthySchema.parse(1); // ✓ Valid
|
|
302
|
+
* TruthySchema.parse('hello'); // ✓ Valid
|
|
303
|
+
* TruthySchema.parse([]); // ✓ Valid (arrays are truthy)
|
|
304
|
+
* TruthySchema.parse({}); // ✓ Valid (objects are truthy)
|
|
305
|
+
* TruthySchema.parse(false); // ✗ Throws validation error
|
|
306
|
+
* TruthySchema.parse(0); // ✗ Throws validation error
|
|
307
|
+
* TruthySchema.parse(''); // ✗ Throws validation error
|
|
308
|
+
* TruthySchema.parse(null); // ✗ Throws validation error
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export const TruthySchema = z
|
|
312
|
+
.any()
|
|
313
|
+
.nonoptional()
|
|
314
|
+
.refine((value) => !!value)
|
|
315
|
+
.describe('Truthy value')
|
|
316
|
+
.register(BupkisRegistry, {
|
|
317
|
+
name: 'Truthy',
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* A Zod schema that validates falsy JavaScript values.
|
|
322
|
+
*
|
|
323
|
+
* This schema accepts any input value but only validates successfully if the
|
|
324
|
+
* value is falsy according to JavaScript's truthiness rules. The falsy values
|
|
325
|
+
* in JavaScript are: `false`, `0`, `-0`, `0n`, `""` (empty string), `null`,
|
|
326
|
+
* `undefined`, and `NaN`.
|
|
327
|
+
*
|
|
328
|
+
* @remarks
|
|
329
|
+
* The schema is registered in the `BupkisRegistry` with the name `Falsy` and
|
|
330
|
+
* indicates that it accepts anything as valid input for evaluation.
|
|
331
|
+
* @category Schema
|
|
332
|
+
* @example
|
|
333
|
+
*
|
|
334
|
+
* ```typescript
|
|
335
|
+
* FalsySchema.parse(false); // ✓ Valid
|
|
336
|
+
* FalsySchema.parse(0); // ✓ Valid
|
|
337
|
+
* FalsySchema.parse(-0); // ✓ Valid
|
|
338
|
+
* FalsySchema.parse(0n); // ✓ Valid (BigInt zero)
|
|
339
|
+
* FalsySchema.parse(''); // ✓ Valid (empty string)
|
|
340
|
+
* FalsySchema.parse(null); // ✓ Valid
|
|
341
|
+
* FalsySchema.parse(undefined); // ✓ Valid
|
|
342
|
+
* FalsySchema.parse(NaN); // ✓ Valid
|
|
343
|
+
* FalsySchema.parse(true); // ✗ Throws validation error
|
|
344
|
+
* FalsySchema.parse(1); // ✗ Throws validation error
|
|
345
|
+
* FalsySchema.parse('hello'); // ✗ Throws validation error
|
|
346
|
+
* FalsySchema.parse({}); // ✗ Throws validation error
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
export const FalsySchema = z
|
|
350
|
+
.any()
|
|
351
|
+
.nullable()
|
|
352
|
+
.refine((value) => !value)
|
|
353
|
+
.describe('Falsy value')
|
|
354
|
+
.register(BupkisRegistry, { name: 'Falsy' });
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* A Zod schema that validates primitive JavaScript values.
|
|
358
|
+
*
|
|
359
|
+
* This schema validates any of the seven primitive data types in JavaScript:
|
|
360
|
+
* string, number, boolean, bigint, symbol, null, and undefined. Primitive
|
|
361
|
+
* values are immutable and are passed by value rather than by reference,
|
|
362
|
+
* distinguishing them from objects and functions which are non-primitive
|
|
363
|
+
* reference types.
|
|
364
|
+
*
|
|
365
|
+
* @remarks
|
|
366
|
+
* The schema is registered in the `BupkisRegistry` with the name `Primitive`
|
|
367
|
+
* and indicates that it accepts primitive values as valid input.
|
|
368
|
+
* @category Schema
|
|
369
|
+
* @example
|
|
370
|
+
*
|
|
371
|
+
* ```typescript
|
|
372
|
+
* PrimitiveSchema.parse('hello'); // ✓ Valid (string)
|
|
373
|
+
* PrimitiveSchema.parse(42); // ✓ Valid (number)
|
|
374
|
+
* PrimitiveSchema.parse(true); // ✓ Valid (boolean)
|
|
375
|
+
* PrimitiveSchema.parse(BigInt(123)); // ✓ Valid (bigint)
|
|
376
|
+
* PrimitiveSchema.parse(Symbol('test')); // ✓ Valid (symbol)
|
|
377
|
+
* PrimitiveSchema.parse(null); // ✓ Valid (null)
|
|
378
|
+
* PrimitiveSchema.parse(undefined); // ✓ Valid (undefined)
|
|
379
|
+
* PrimitiveSchema.parse({}); // ✗ Throws validation error (object)
|
|
380
|
+
* PrimitiveSchema.parse([]); // ✗ Throws validation error (array)
|
|
381
|
+
* PrimitiveSchema.parse(() => {}); // ✗ Throws validation error (function)
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
export const PrimitiveSchema = z
|
|
385
|
+
.union([
|
|
386
|
+
z.string(),
|
|
387
|
+
z.number(),
|
|
388
|
+
z.boolean(),
|
|
389
|
+
z.bigint(),
|
|
390
|
+
z.symbol(),
|
|
391
|
+
z.null(),
|
|
392
|
+
z.undefined(),
|
|
393
|
+
])
|
|
394
|
+
.describe('Primitive value')
|
|
395
|
+
.register(BupkisRegistry, { name: 'Primitive' });
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* A Zod schema that validates array-like structures including mutable and
|
|
399
|
+
* readonly variants.
|
|
400
|
+
*
|
|
401
|
+
* This schema validates values that behave like arrays, including standard
|
|
402
|
+
* arrays, tuples with rest elements, and their readonly counterparts. It
|
|
403
|
+
* accepts any array-like structure that can hold elements of any type, making
|
|
404
|
+
* it useful for validating collections where the specific array mutability or
|
|
405
|
+
* tuple structure is not critical.
|
|
406
|
+
*
|
|
407
|
+
* @remarks
|
|
408
|
+
* The schema is registered in the {@link BupkisRegistry} with the name
|
|
409
|
+
* `ArrayLike` for later reference and type checking purposes. This schema is
|
|
410
|
+
* particularly useful when you need to accept various forms of array-like data
|
|
411
|
+
* without being restrictive about mutability or exact tuple structure.
|
|
412
|
+
* @category Schema
|
|
413
|
+
* @example
|
|
414
|
+
*
|
|
415
|
+
* ```typescript
|
|
416
|
+
* ArrayLikeSchema.parse([1, 2, 3]); // ✓ Valid (mutable array)
|
|
417
|
+
* ArrayLikeSchema.parse(['a', 'b'] as const); // ✓ Valid (readonly array)
|
|
418
|
+
* ArrayLikeSchema.parse([]); // ✓ Valid (empty array)
|
|
419
|
+
* ArrayLikeSchema.parse([42, 'mixed', true]); // ✓ Valid (mixed types)
|
|
420
|
+
* ArrayLikeSchema.parse('not an array'); // ✗ Throws validation error
|
|
421
|
+
* ArrayLikeSchema.parse({}); // ✗ Throws validation error
|
|
422
|
+
* ArrayLikeSchema.parse(null); // ✗ Throws validation error
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
export const ArrayLikeSchema = z
|
|
426
|
+
.union([
|
|
427
|
+
z.array(z.any()),
|
|
428
|
+
z.tuple([z.any()], z.any()),
|
|
429
|
+
z.looseObject({ length: z.number().nonnegative().int() }),
|
|
430
|
+
])
|
|
431
|
+
.describe('Array-like value')
|
|
432
|
+
.register(BupkisRegistry, {
|
|
433
|
+
name: 'ArrayLike',
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* A Zod schema that validates RegExp instances.
|
|
438
|
+
*
|
|
439
|
+
* This schema validates values that are instances of the RegExp class,
|
|
440
|
+
* including regular expressions created with both literal syntax
|
|
441
|
+
* (`/pattern/flags`) and the RegExp constructor (`new RegExp(pattern, flags)`).
|
|
442
|
+
* It ensures the validated value is a proper regular expression object with all
|
|
443
|
+
* associated methods and properties.
|
|
444
|
+
*
|
|
445
|
+
* @remarks
|
|
446
|
+
* The schema is registered in the `BupkisRegistry` with the name `RegExp` for
|
|
447
|
+
* later reference and type checking purposes.
|
|
448
|
+
* @category Schema
|
|
449
|
+
* @example
|
|
450
|
+
*
|
|
451
|
+
* ```typescript
|
|
452
|
+
* RegExpSchema.parse(/abc/gi); // ✓ Valid (literal syntax)
|
|
453
|
+
* RegExpSchema.parse(new RegExp('abc', 'gi')); // ✓ Valid (constructor)
|
|
454
|
+
* RegExpSchema.parse(/test/); // ✓ Valid (no flags)
|
|
455
|
+
* RegExpSchema.parse(new RegExp('')); // ✓ Valid (empty pattern)
|
|
456
|
+
* RegExpSchema.parse('abc'); // ✗ Throws validation error (string)
|
|
457
|
+
* RegExpSchema.parse(/abc/.source); // ✗ Throws validation error (string pattern)
|
|
458
|
+
* RegExpSchema.parse({}); // ✗ Throws validation error (object)
|
|
459
|
+
* ```
|
|
460
|
+
*/
|
|
461
|
+
export const RegExpSchema = z
|
|
462
|
+
.instanceof(RegExp)
|
|
463
|
+
.describe('A RegExp instance')
|
|
464
|
+
.register(BupkisRegistry, { name: 'RegExp' });
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types used throughout _BUPKIS_, mainly related to the
|
|
3
|
+
* `expect()`/`expectAsync()` API.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
ArrayValues,
|
|
10
|
+
NonEmptyTuple,
|
|
11
|
+
Constructor as TypeFestConstructor,
|
|
12
|
+
} from 'type-fest';
|
|
13
|
+
import type { z } from 'zod/v4';
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
AnyAssertion,
|
|
17
|
+
AnyAsyncAssertion,
|
|
18
|
+
AnyAsyncAssertions,
|
|
19
|
+
AnySyncAssertion,
|
|
20
|
+
AnySyncAssertions,
|
|
21
|
+
AssertionPart,
|
|
22
|
+
AssertionParts,
|
|
23
|
+
AssertionSlot,
|
|
24
|
+
NoNeverTuple,
|
|
25
|
+
PhraseLiteral,
|
|
26
|
+
PhraseLiteralChoice,
|
|
27
|
+
PhraseLiteralChoiceSlot,
|
|
28
|
+
PhraseLiteralSlot,
|
|
29
|
+
} from './assertion/assertion-types.js';
|
|
30
|
+
|
|
31
|
+
import { type Expect, type ExpectAsync } from './api.js';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Helper type to create negated version of assertion parts. For phrase
|
|
35
|
+
* literals, creates "not phrase" version. For phrase arrays, creates negated
|
|
36
|
+
* versions of each phrase in the array.
|
|
37
|
+
*/
|
|
38
|
+
export type AddNegation<T extends readonly AssertionPart[]> =
|
|
39
|
+
T extends readonly [
|
|
40
|
+
infer First extends AssertionPart,
|
|
41
|
+
...infer Rest extends readonly AssertionPart[],
|
|
42
|
+
]
|
|
43
|
+
? First extends NonEmptyTuple<string>
|
|
44
|
+
? readonly [
|
|
45
|
+
{
|
|
46
|
+
[K in keyof First]: First[K] extends string
|
|
47
|
+
? Negation<First[K]>
|
|
48
|
+
: never;
|
|
49
|
+
},
|
|
50
|
+
...AddNegation<Rest>,
|
|
51
|
+
]
|
|
52
|
+
: First extends string
|
|
53
|
+
? readonly [Negation<First>, ...AddNegation<Rest>]
|
|
54
|
+
: readonly [First, ...AddNegation<Rest>]
|
|
55
|
+
: readonly [];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Helper type to concatenate two tuples
|
|
59
|
+
*/
|
|
60
|
+
export type Concat<
|
|
61
|
+
A extends readonly unknown[],
|
|
62
|
+
B extends readonly unknown[],
|
|
63
|
+
> = readonly [...A, ...B];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A constructor based on {@link TypeFestConstructor type-fest's Constructor}
|
|
67
|
+
* with a default instance type argument.
|
|
68
|
+
*/
|
|
69
|
+
export type Constructor<
|
|
70
|
+
T = any,
|
|
71
|
+
Arguments extends unknown[] = any[],
|
|
72
|
+
> = TypeFestConstructor<T, Arguments>;
|
|
73
|
+
|
|
74
|
+
export type FilterAsyncAssertions<T extends readonly AnyAssertion[]> =
|
|
75
|
+
T extends readonly [
|
|
76
|
+
infer S extends AnyAssertion,
|
|
77
|
+
...infer Rest extends readonly AnyAssertion[],
|
|
78
|
+
]
|
|
79
|
+
? S extends AnyAsyncAssertion
|
|
80
|
+
? readonly [S, ...FilterAsyncAssertions<Rest>]
|
|
81
|
+
: FilterAsyncAssertions<Rest>
|
|
82
|
+
: readonly [];
|
|
83
|
+
|
|
84
|
+
export type FilterSyncAssertions<T extends readonly AnyAssertion[]> =
|
|
85
|
+
T extends readonly [
|
|
86
|
+
infer S extends AnyAssertion,
|
|
87
|
+
...infer Rest extends readonly AnyAssertion[],
|
|
88
|
+
]
|
|
89
|
+
? S extends AnySyncAssertion
|
|
90
|
+
? readonly [S, ...FilterSyncAssertions<Rest>]
|
|
91
|
+
: FilterSyncAssertions<Rest>
|
|
92
|
+
: readonly [];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Prepares {@link MapExpectSlots} by injecting `unknown` if the `AssertionParts`
|
|
96
|
+
* have no `z.ZodType` in the head position.
|
|
97
|
+
*
|
|
98
|
+
* Also filters out `never` from the resulting tuple to guarantee tupleness.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* This is a convenience and I hope it's not too confusing.
|
|
102
|
+
*/
|
|
103
|
+
export type InferredExpectSlots<Parts extends AssertionParts> = NoNeverTuple<
|
|
104
|
+
Parts extends readonly [infer First extends AssertionPart, ...infer _]
|
|
105
|
+
? First extends PhraseLiteral | PhraseLiteralChoice
|
|
106
|
+
? [unknown, ...MapExpectSlots<Parts>]
|
|
107
|
+
: MapExpectSlots<Parts>
|
|
108
|
+
: never
|
|
109
|
+
>; /**
|
|
110
|
+
* Maps `AssertionParts` to the corresponding argument types for `expect` and
|
|
111
|
+
* `expectAsync`, as provided by the user.
|
|
112
|
+
*
|
|
113
|
+
* Overloads each phrase literal slot with a negated version using a union.
|
|
114
|
+
*/
|
|
115
|
+
export type MapExpectSlots<Parts extends readonly AssertionPart[]> =
|
|
116
|
+
Parts extends readonly [
|
|
117
|
+
infer First extends AssertionPart,
|
|
118
|
+
...infer Rest extends readonly AssertionPart[],
|
|
119
|
+
]
|
|
120
|
+
? readonly [
|
|
121
|
+
AssertionSlot<First> extends PhraseLiteralSlot<infer StringLiteral>
|
|
122
|
+
? Negation<StringLiteral> | StringLiteral
|
|
123
|
+
: AssertionSlot<First> extends PhraseLiteralChoiceSlot<
|
|
124
|
+
infer StringLiterals
|
|
125
|
+
>
|
|
126
|
+
?
|
|
127
|
+
| ArrayValues<StringLiterals>
|
|
128
|
+
| Negation<ArrayValues<StringLiterals>>
|
|
129
|
+
: AssertionSlot<First> extends z.ZodType
|
|
130
|
+
? z.infer<AssertionSlot<First>>
|
|
131
|
+
: never,
|
|
132
|
+
...MapExpectSlots<Rest>,
|
|
133
|
+
]
|
|
134
|
+
: readonly [];
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Makes tuple types accept both mutable and readonly variants
|
|
138
|
+
*/
|
|
139
|
+
export type MutableOrReadonly<T> = T extends readonly (infer U)[]
|
|
140
|
+
? readonly U[] | U[]
|
|
141
|
+
: T extends readonly [infer First, ...infer Rest]
|
|
142
|
+
? [First, ...Rest] | readonly [First, ...Rest]
|
|
143
|
+
: T;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The type of a `PhraesLiteral` which is negated, e.g. "not to be"
|
|
147
|
+
*/
|
|
148
|
+
export type Negation<T extends string> = `not ${T}`;
|
|
149
|
+
|
|
150
|
+
export type UseFn<T extends AnySyncAssertions, U extends AnyAsyncAssertions> = <
|
|
151
|
+
V extends readonly AnyAssertion[],
|
|
152
|
+
W extends FilterSyncAssertions<V>,
|
|
153
|
+
X extends FilterAsyncAssertions<V>,
|
|
154
|
+
>(
|
|
155
|
+
assertions: V,
|
|
156
|
+
) => {
|
|
157
|
+
expect: Expect<Concat<T, W>, Concat<U, X>>;
|
|
158
|
+
expectAsync: ExpectAsync<Concat<U, X>, Concat<T, W>>;
|
|
159
|
+
};
|