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.
@@ -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
+ };