bupkis 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +26 -2
  3. package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
  4. package/dist/commonjs/assertion/impl/async.js +6 -2
  5. package/dist/commonjs/assertion/impl/async.js.map +1 -1
  6. package/dist/commonjs/assertion/impl/callback.js +2 -2
  7. package/dist/commonjs/assertion/impl/callback.js.map +1 -1
  8. package/dist/commonjs/assertion/impl/sync-basic.d.ts.map +1 -1
  9. package/dist/commonjs/assertion/impl/sync-basic.js +1 -1
  10. package/dist/commonjs/assertion/impl/sync-basic.js.map +1 -1
  11. package/dist/commonjs/assertion/impl/sync-collection.d.ts +1 -1
  12. package/dist/commonjs/assertion/impl/sync-collection.js +3 -3
  13. package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
  14. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +5 -14
  15. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  16. package/dist/commonjs/assertion/impl/sync-parametric.js +5 -5
  17. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  18. package/dist/commonjs/assertion/impl/sync.d.ts +6 -15
  19. package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
  20. package/dist/commonjs/bootstrap.d.ts +6 -15
  21. package/dist/commonjs/bootstrap.d.ts.map +1 -1
  22. package/dist/commonjs/constant.d.ts +1 -1
  23. package/dist/commonjs/constant.d.ts.map +1 -1
  24. package/dist/commonjs/constant.js +2 -1
  25. package/dist/commonjs/constant.js.map +1 -1
  26. package/dist/commonjs/guards.d.ts +74 -1
  27. package/dist/commonjs/guards.d.ts.map +1 -1
  28. package/dist/commonjs/guards.js +78 -1
  29. package/dist/commonjs/guards.js.map +1 -1
  30. package/dist/commonjs/index.d.ts +7 -16
  31. package/dist/commonjs/index.d.ts.map +1 -1
  32. package/dist/commonjs/index.js.map +1 -1
  33. package/dist/commonjs/schema.d.ts +66 -10
  34. package/dist/commonjs/schema.d.ts.map +1 -1
  35. package/dist/commonjs/schema.js +87 -12
  36. package/dist/commonjs/schema.js.map +1 -1
  37. package/dist/commonjs/types.d.ts +132 -1
  38. package/dist/commonjs/types.d.ts.map +1 -1
  39. package/dist/commonjs/use.d.ts.map +1 -1
  40. package/dist/commonjs/use.js +15 -1
  41. package/dist/commonjs/use.js.map +1 -1
  42. package/dist/commonjs/value-to-schema.d.ts.map +1 -1
  43. package/dist/commonjs/value-to-schema.js +20 -0
  44. package/dist/commonjs/value-to-schema.js.map +1 -1
  45. package/dist/esm/assertion/impl/async.d.ts.map +1 -1
  46. package/dist/esm/assertion/impl/async.js +7 -3
  47. package/dist/esm/assertion/impl/async.js.map +1 -1
  48. package/dist/esm/assertion/impl/callback.js +3 -3
  49. package/dist/esm/assertion/impl/callback.js.map +1 -1
  50. package/dist/esm/assertion/impl/sync-basic.d.ts.map +1 -1
  51. package/dist/esm/assertion/impl/sync-basic.js +2 -2
  52. package/dist/esm/assertion/impl/sync-basic.js.map +1 -1
  53. package/dist/esm/assertion/impl/sync-collection.d.ts +1 -1
  54. package/dist/esm/assertion/impl/sync-collection.js +3 -3
  55. package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
  56. package/dist/esm/assertion/impl/sync-parametric.d.ts +5 -14
  57. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  58. package/dist/esm/assertion/impl/sync-parametric.js +6 -6
  59. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  60. package/dist/esm/assertion/impl/sync.d.ts +6 -15
  61. package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
  62. package/dist/esm/bootstrap.d.ts +6 -15
  63. package/dist/esm/bootstrap.d.ts.map +1 -1
  64. package/dist/esm/constant.d.ts +1 -1
  65. package/dist/esm/constant.d.ts.map +1 -1
  66. package/dist/esm/constant.js +1 -0
  67. package/dist/esm/constant.js.map +1 -1
  68. package/dist/esm/guards.d.ts +74 -1
  69. package/dist/esm/guards.d.ts.map +1 -1
  70. package/dist/esm/guards.js +76 -0
  71. package/dist/esm/guards.js.map +1 -1
  72. package/dist/esm/index.d.ts +7 -16
  73. package/dist/esm/index.d.ts.map +1 -1
  74. package/dist/esm/index.js.map +1 -1
  75. package/dist/esm/schema.d.ts +66 -10
  76. package/dist/esm/schema.d.ts.map +1 -1
  77. package/dist/esm/schema.js +87 -12
  78. package/dist/esm/schema.js.map +1 -1
  79. package/dist/esm/types.d.ts +132 -1
  80. package/dist/esm/types.d.ts.map +1 -1
  81. package/dist/esm/use.d.ts.map +1 -1
  82. package/dist/esm/use.js +15 -1
  83. package/dist/esm/use.js.map +1 -1
  84. package/dist/esm/value-to-schema.d.ts.map +1 -1
  85. package/dist/esm/value-to-schema.js +21 -1
  86. package/dist/esm/value-to-schema.js.map +1 -1
  87. package/package.json +1 -1
  88. package/src/assertion/impl/async.ts +7 -3
  89. package/src/assertion/impl/callback.ts +3 -3
  90. package/src/assertion/impl/sync-basic.ts +5 -2
  91. package/src/assertion/impl/sync-collection.ts +3 -3
  92. package/src/assertion/impl/sync-parametric.ts +7 -6
  93. package/src/constant.ts +2 -0
  94. package/src/guards.ts +81 -1
  95. package/src/index.ts +2 -0
  96. package/src/schema.ts +99 -12
  97. package/src/types.ts +165 -2
  98. package/src/use.ts +22 -0
  99. package/src/value-to-schema.ts +25 -0
package/src/schema.ts CHANGED
@@ -37,15 +37,21 @@ import { z } from 'zod/v4';
37
37
  import {
38
38
  isA,
39
39
  isConstructible,
40
+ isExpectItExecutor,
40
41
  isFunction,
41
42
  isNonNullObject,
42
43
  isPromiseLike,
43
44
  } from './guards.js';
44
45
  import { BupkisRegistry } from './metadata.js';
45
- import { type Constructor, type MutableOrReadonly } from './types.js';
46
+ import {
47
+ type Constructor,
48
+ type ExpectItExecutor,
49
+ type MutableOrReadonly,
50
+ type SatisfyPatternValue,
51
+ } from './types.js';
46
52
 
47
53
  /**
48
- * A Zod schema that validates JavaScript classes or constructor functions.
54
+ * A Zod schema that validates JavaScript constructible functions.
49
55
  *
50
56
  * This schema validates values that can be used as constructors, including ES6
51
57
  * classes, traditional constructor functions, and built-in constructors. It
@@ -54,28 +60,28 @@ import { type Constructor, type MutableOrReadonly } from './types.js';
54
60
  *
55
61
  * @privateRemarks
56
62
  * The schema is registered in the {@link BupkisRegistry} with the name
57
- * `ClassSchema` for later reference and type checking purposes.
63
+ * `ConstructibleSchema` for later reference and type checking purposes.
58
64
  * @example
59
65
  *
60
66
  * ```typescript
61
67
  * class MyClass {}
62
68
  * function MyConstructor() {}
63
69
  *
64
- * ClassSchema.parse(MyClass); // ✓ Valid
65
- * ClassSchema.parse(MyConstructor); // ✓ Valid
66
- * ClassSchema.parse(Array); // ✓ Valid
67
- * ClassSchema.parse(Date); // ✓ Valid
68
- * ClassSchema.parse(() => {}); // ✗ Throws validation error
69
- * ClassSchema.parse({}); // ✗ Throws validation error
70
+ * ConstructibleSchema.parse(MyClass); // ✓ Valid
71
+ * ConstructibleSchema.parse(MyConstructor); // ✓ Valid
72
+ * ConstructibleSchema.parse(Array); // ✓ Valid
73
+ * ConstructibleSchema.parse(Date); // ✓ Valid
74
+ * ConstructibleSchema.parse(() => {}); // ✗ Throws validation error
75
+ * ConstructibleSchema.parse({}); // ✗ Throws validation error
70
76
  * ```
71
77
  *
72
78
  * @group Schema
73
79
  */
74
80
 
75
- export const ClassSchema = z
81
+ export const ConstructibleSchema = z
76
82
  .custom<Constructor>(isConstructible)
77
- .register(BupkisRegistry, { name: 'ClassSchema' })
78
- .describe('Class / Constructor');
83
+ .register(BupkisRegistry, { name: 'ConstructibleSchema' })
84
+ .describe('Constructible Function');
79
85
 
80
86
  /**
81
87
  * A Zod schema that validates any JavaScript function.
@@ -506,3 +512,84 @@ export const RegExpSchema = z
506
512
  .instanceof(RegExp)
507
513
  .describe('A RegExp instance')
508
514
  .register(BupkisRegistry, { name: 'RegExp' });
515
+
516
+ /**
517
+ * A recursive Zod schema that validates values allowed in "to satisfy"
518
+ * assertion patterns.
519
+ *
520
+ * This schema defines the allowed structure for patterns used with "to satisfy"
521
+ * and "to be like" assertions. It supports a recursive structure that can
522
+ * contain:
523
+ *
524
+ * - **Primitive values**: strings, numbers, booleans, null, undefined, bigint,
525
+ * symbols
526
+ * - **Regular expressions**: for pattern matching against string values
527
+ * - **ExpectItExecutor functions**: nested assertions created via `expect.it()`
528
+ * - **Objects**: with properties that recursively follow this same pattern
529
+ * - **Arrays**: with elements that recursively follow this same pattern
530
+ *
531
+ * The schema handles the special semantics required for "to satisfy"
532
+ * assertions:
533
+ *
534
+ * - RegExp values are used for pattern testing rather than exact matching
535
+ * - ExpectItExecutor functions are executed as nested assertions
536
+ * - Objects and arrays can contain any combination of the above recursively
537
+ *
538
+ * @privateRemarks
539
+ * Uses `z.lazy()` to handle recursive object and array structures without
540
+ * causing infinite recursion during schema definition. The schema is registered
541
+ * in the `BupkisRegistry` for reference and type checking.
542
+ * @example
543
+ *
544
+ * ```typescript
545
+ * // Primitive values
546
+ * SatisfyPatternSchema.parse('hello'); // ✓ Valid
547
+ * SatisfyPatternSchema.parse(42); // ✓ Valid
548
+ * SatisfyPatternSchema.parse(true); // ✓ Valid
549
+ *
550
+ * // Regular expressions for pattern matching
551
+ * SatisfyPatternSchema.parse(/test/); // ✓ Valid
552
+ *
553
+ * // ExpectItExecutor functions
554
+ * SatisfyPatternSchema.parse(expect.it('to be a string')); // ✓ Valid
555
+ *
556
+ * // Complex nested structures
557
+ * SatisfyPatternSchema.parse({
558
+ * name: expect.it('to be a string'),
559
+ * email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
560
+ * age: expect.it('to be greater than', 0),
561
+ * tags: [expect.it('to be a string')],
562
+ * }); // ✓ Valid
563
+ *
564
+ * // Invalid values
565
+ * SatisfyPatternSchema.parse(new Date()); // ✗ Invalid (not supported type)
566
+ * SatisfyPatternSchema.parse(() => {}); // ✗ Invalid (regular function, not ExpectItExecutor)
567
+ * ```
568
+ *
569
+ * @group Schema
570
+ */
571
+ export const SatisfyPatternSchema: z.ZodType<SatisfyPatternValue> = z
572
+ .lazy(() =>
573
+ z.union([
574
+ // Primitive types
575
+ z.string(),
576
+ z.number(),
577
+ z.boolean(),
578
+ z.null(),
579
+ z.undefined(),
580
+ z.bigint(),
581
+ z.symbol(),
582
+
583
+ // Special types for "to satisfy" semantics
584
+ z.instanceof(RegExp), // For pattern matching
585
+ z.custom<ExpectItExecutor<any>>(isExpectItExecutor, {
586
+ message:
587
+ 'Expected an ExpectItExecutor function (created with expect.it())',
588
+ }), // For nested assertions
589
+
590
+ // Recursive structures
591
+ z.array(SatisfyPatternSchema), // Arrays of satisfy patterns
592
+ z.record(z.string(), SatisfyPatternSchema), // Objects with satisfy pattern values
593
+ ]),
594
+ )
595
+ .register(BupkisRegistry, { name: 'SatisfyPatternSchema' });
package/src/types.ts CHANGED
@@ -53,6 +53,8 @@ import type {
53
53
  RawAssertionImplSchemaSync,
54
54
  } from './assertion/assertion-types.js';
55
55
 
56
+ import { type kExpectIt } from './constant.js';
57
+
56
58
  /**
57
59
  * Creates a negated version of a tuple of
58
60
  * {@link AssertionPart | AssertionParts}.
@@ -110,8 +112,6 @@ export interface BaseExpect {
110
112
  fail: FailFn;
111
113
  }
112
114
 
113
- export type * from './assertion/assertion-types.js';
114
-
115
115
  /**
116
116
  * The main API as returned by a {@link UseFn}.
117
117
  *
@@ -166,6 +166,8 @@ export interface Bupkis<
166
166
  >;
167
167
  }
168
168
 
169
+ export type * from './assertion/assertion-types.js';
170
+
169
171
  /**
170
172
  * Helper type to concatenate two tuples
171
173
  */
@@ -398,6 +400,125 @@ export type ExpectFunction<
398
400
  }>
399
401
  >;
400
402
 
403
+ /**
404
+ * Creates embeddable assertion functions that can be used with `to satisfy`.
405
+ *
406
+ * This type generates a union of all possible `expect.it` function signatures
407
+ * based on the available synchronous assertions. Each assertion contributes its
408
+ * own function signature to create embeddable executors that can be used within
409
+ * object patterns for complex validation scenarios.
410
+ *
411
+ * The resulting functions are designed to be used exclusively within `'to
412
+ * satisfy'` assertion contexts, where they provide type-safe pattern matching
413
+ * for nested object structures. Direct execution of these functions outside of
414
+ * their intended context is not supported.
415
+ *
416
+ * @example
417
+ *
418
+ * ```typescript
419
+ * // Create embeddable assertion functions
420
+ * const isString = expect.it('to be a string');
421
+ * const isPositive = expect.it('to be greater than', 0);
422
+ *
423
+ * // Use within 'to satisfy' patterns
424
+ * expect(user, 'to satisfy', {
425
+ * name: isString,
426
+ * age: isPositive,
427
+ * email: /\S+@\S+/,
428
+ * });
429
+ * ```
430
+ *
431
+ * @template SyncAssertions - Array of synchronous assertion objects that define
432
+ * the available assertion logic for embeddable functions
433
+ * @see {@link ExpectItFunction} for individual function signature generation
434
+ * @see {@link ExpectItExecutor} for the executor function interface
435
+ */
436
+ export type ExpectIt<
437
+ SyncAssertions extends AnySyncAssertions = BuiltinSyncAssertions,
438
+ > = UnionToIntersection<
439
+ TupleToUnion<{
440
+ [K in keyof SyncAssertions]: SyncAssertions[K] extends AnySyncAssertion
441
+ ? SyncAssertions[K]['parts'] extends AssertionParts
442
+ ? ExpectItFunction<SyncAssertions[K]['parts']>
443
+ : never
444
+ : never;
445
+ }>
446
+ >;
447
+
448
+ /**
449
+ * Interface for executor functions created by `expect.it()`.
450
+ *
451
+ * ExpectItExecutor functions are the result of calling `expect.it()` with
452
+ * assertion parameters. They encapsulate the assertion logic and can be
453
+ * executed later within `'to satisfy'` pattern matching contexts. These
454
+ * functions are marked with an internal symbol to distinguish them from regular
455
+ * functions during pattern validation.
456
+ *
457
+ * The executor accepts a subject value and performs the embedded assertion
458
+ * logic against it. The subject type is constrained by the Zod schema that
459
+ * represents the first part of the assertion definition, ensuring type safety
460
+ * during pattern matching.
461
+ *
462
+ * @example
463
+ *
464
+ * ```typescript
465
+ * const isStringExecutor = expect.it('to be a string');
466
+ * // isStringExecutor is an ExpectItExecutor<z.ZodString>
467
+ *
468
+ * // Used within satisfy patterns
469
+ * expect({ name: 'Alice' }, 'to satisfy', {
470
+ * name: isStringExecutor, // Validates that name is a string
471
+ * });
472
+ * ```
473
+ *
474
+ * @template Subject - The Zod schema type that constrains the subject parameter
475
+ * @see {@link ExpectItFunction} for the factory function that creates executors
476
+ */
477
+ export interface ExpectItExecutor<Subject extends z.ZodType> {
478
+ (subject: z.infer<Subject>): void;
479
+ [kExpectIt]: true;
480
+ }
481
+
482
+ /**
483
+ * Function signature for creating ExpectItExecutor instances.
484
+ *
485
+ * This type represents the factory function that creates embeddable assertion
486
+ * executors from assertion parts. It takes the assertion parameters (excluding
487
+ * the subject) and returns an executor function that can be embedded within
488
+ * `'to satisfy'` patterns.
489
+ *
490
+ * The function signature is derived from assertion parts by removing the first
491
+ * part (which becomes the subject type for the executor) and using the
492
+ * remaining parts as parameters. This allows for natural language assertion
493
+ * creation that mirrors the main `expect()` function but produces reusable
494
+ * executor functions.
495
+ *
496
+ * The resulting executor is constrained to only work with subjects that match
497
+ * the first assertion part, providing compile-time type safety for pattern
498
+ * matching scenarios.
499
+ *
500
+ * @example
501
+ *
502
+ * ```typescript
503
+ * // For assertion parts: [z.string(), 'to match', z.instanceof(RegExp)]
504
+ * // Results in function: (pattern: RegExp) => ExpectItExecutor<z.ZodString>
505
+ * const matchesPattern = expect.it('to match', /^[A-Z]/);
506
+ *
507
+ * expect({ code: 'ABC123' }, 'to satisfy', {
508
+ * code: matchesPattern, // Validates that code matches the pattern
509
+ * });
510
+ * ```
511
+ *
512
+ * @template Parts - Tuple of assertion parts that define the function signature
513
+ * and executor constraints
514
+ * @see {@link ExpectItExecutor} for the returned executor interface
515
+ * @see {@link TupleTail} for parameter extraction from assertion parts
516
+ * @see {@link SlotsFromParts} for type slot generation
517
+ */
518
+ export type ExpectItFunction<Parts extends AssertionParts> = (
519
+ ...args: MutableOrReadonly<TupleTail<SlotsFromParts<Parts>>>
520
+ ) => Parts[0] extends z.ZodType ? ExpectItExecutor<Parts[0]> : never;
521
+
401
522
  /**
402
523
  * Properties of {@link expect}.
403
524
  */
@@ -412,6 +533,8 @@ export interface ExpectSyncProps<
412
533
  */
413
534
  assertions: SyncAssertions;
414
535
 
536
+ it: ExpectIt<SyncAssertions>;
537
+
415
538
  /**
416
539
  * Function to add more assertions to this `expect()`, returning a new
417
540
  * `expect()` and `expectAsync()` pair with the combined assertions.
@@ -600,6 +723,23 @@ export type MutableOrReadonly<Tuple extends readonly unknown[]> =
600
723
  */
601
724
  export type Negation<S extends string> = `not ${S}`;
602
725
 
726
+ export type SatisfyPatternValue =
727
+ | bigint
728
+ | boolean
729
+ | ExpectItExecutor<any>
730
+ | Map<SatisfyPatternValue, SatisfyPatternValue>
731
+ | null
732
+ | number
733
+ | RegExp
734
+ | SatisfyPatternValue[]
735
+ | Set<SatisfyPatternValue>
736
+ | string
737
+ | symbol
738
+ | undefined
739
+ | WeakMap<Extract<SatisfyPatternValue, object>, SatisfyPatternValue>
740
+ | WeakSet<Extract<SatisfyPatternValue, object>>
741
+ | { [key: string]: SatisfyPatternValue };
742
+
603
743
  /**
604
744
  * Converts AssertionParts to complete function parameter types for expect
605
745
  * functions.
@@ -646,6 +786,29 @@ export type SlotsFromParts<Parts extends AssertionParts> = NoNeverTuple<
646
786
  : never
647
787
  >;
648
788
 
789
+ /**
790
+ * Gets the tail (all elements except the first) of a tuple type.
791
+ *
792
+ * Unlike ArrayTail from type-fest, this preserves the tuple structure as a
793
+ * readonly tuple rather than converting to an array type.
794
+ *
795
+ * @example
796
+ *
797
+ * ```typescript
798
+ * type Example = TupleTail<readonly [string, number, boolean]>; // readonly [number, boolean]
799
+ * type Single = TupleTail<readonly [string]>; // readonly []
800
+ * type Empty = TupleTail<readonly []>; // readonly []
801
+ * ```
802
+ *
803
+ * @template T - The tuple type to get the tail of
804
+ */
805
+ export type TupleTail<T extends readonly unknown[]> = T extends readonly [
806
+ unknown,
807
+ ...infer Rest,
808
+ ]
809
+ ? Rest
810
+ : readonly [];
811
+
649
812
  /**
650
813
  * The type of a `use()` function.
651
814
  */
package/src/use.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  type AnyAsyncAssertions,
6
6
  type AnySyncAssertions,
7
7
  } from './assertion/assertion-types.js';
8
+ import { kExpectIt } from './constant.js';
8
9
  import {
9
10
  createBaseExpect,
10
11
  createExpectAsyncFunction,
@@ -12,11 +13,31 @@ import {
12
13
  } from './expect.js';
13
14
  import {
14
15
  type Concat,
16
+ type ExpectIt,
15
17
  type FilterAsyncAssertions,
16
18
  type FilterSyncAssertions,
17
19
  type UseFn,
18
20
  } from './types.js';
19
21
 
22
+ const createExpectIt = <SyncAssertions extends AnySyncAssertions>(
23
+ expect: any,
24
+ ): ExpectIt<SyncAssertions> => {
25
+ const expectIt = (...args: readonly unknown[]) => {
26
+ const func = Object.assign(
27
+ (subject: unknown) => {
28
+ const allArgs = [subject, ...args];
29
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
30
+ expect(...allArgs);
31
+ },
32
+ {
33
+ [kExpectIt]: true,
34
+ },
35
+ );
36
+ return func;
37
+ };
38
+ return expectIt as unknown as ExpectIt<SyncAssertions>;
39
+ };
40
+
20
41
  export function createUse<
21
42
  const T extends AnySyncAssertions,
22
43
  const U extends AnyAsyncAssertions,
@@ -50,6 +71,7 @@ export function createUse<
50
71
  const expect = Object.assign(
51
72
  expectFunction,
52
73
  createBaseExpect(allSyncAssertions, allAsyncAssertions, 'sync'),
74
+ { it: createExpectIt(expectFunction) },
53
75
  );
54
76
  const expectAsync = Object.assign(
55
77
  expectAsyncFunction,
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod/v4';
2
2
 
3
3
  import {
4
+ isExpectItExecutor,
4
5
  isNonNullObject,
5
6
  isObject,
6
7
  isPromiseLike,
@@ -100,6 +101,30 @@ export const valueToSchema = (
100
101
  case 'boolean':
101
102
  return literalPrimitives ? z.literal(value as boolean) : z.boolean();
102
103
  case 'function':
104
+ // Check if this is an ExpectItExecutor
105
+ if (isExpectItExecutor(value)) {
106
+ // Only allow nested assertions when strict is false (e.g., "to satisfy" semantics)
107
+ if (strict) {
108
+ throw new TypeError(
109
+ 'ExpectItExecutor (expect.it) functions are not allowed in strict mode. ' +
110
+ 'Use "to satisfy" assertions for nested expectations.',
111
+ );
112
+ }
113
+ // Return a schema that executes the ExpectItExecutor when validated
114
+ return z.custom<unknown>(
115
+ (subject: unknown) => {
116
+ try {
117
+ value(subject);
118
+ return true;
119
+ } catch {
120
+ return false;
121
+ }
122
+ },
123
+ {
124
+ message: 'Failed expect.it assertion',
125
+ },
126
+ );
127
+ }
103
128
  return z.function();
104
129
  case 'number':
105
130
  return literalPrimitives ? z.literal(value as number) : z.number();