bupkis 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +9 -9
  3. package/dist/commonjs/assertion/assertion-async.d.ts +2 -1
  4. package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
  5. package/dist/commonjs/assertion/assertion-async.js +84 -2
  6. package/dist/commonjs/assertion/assertion-async.js.map +1 -1
  7. package/dist/commonjs/assertion/assertion-sync.d.ts +1 -1
  8. package/dist/commonjs/assertion/assertion-sync.d.ts.map +1 -1
  9. package/dist/commonjs/assertion/assertion-sync.js +5 -1
  10. package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
  11. package/dist/commonjs/assertion/assertion-types.d.ts +6 -2
  12. package/dist/commonjs/assertion/assertion-types.d.ts.map +1 -1
  13. package/dist/commonjs/assertion/assertion.d.ts +1 -1
  14. package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
  15. package/dist/commonjs/assertion/assertion.js +1 -14
  16. package/dist/commonjs/assertion/assertion.js.map +1 -1
  17. package/dist/commonjs/assertion/impl/async.d.ts +122 -21
  18. package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
  19. package/dist/commonjs/assertion/impl/async.js +112 -88
  20. package/dist/commonjs/assertion/impl/async.js.map +1 -1
  21. package/dist/commonjs/assertion/impl/callback.d.ts +104 -0
  22. package/dist/commonjs/assertion/impl/callback.d.ts.map +1 -0
  23. package/dist/commonjs/assertion/impl/callback.js +694 -0
  24. package/dist/commonjs/assertion/impl/callback.js.map +1 -0
  25. package/dist/commonjs/assertion/impl/index.d.ts +1 -1
  26. package/dist/commonjs/assertion/impl/index.d.ts.map +1 -1
  27. package/dist/commonjs/assertion/impl/index.js.map +1 -1
  28. package/dist/commonjs/assertion/impl/sync-esoteric.js +1 -1
  29. package/dist/commonjs/assertion/impl/sync-esoteric.js.map +1 -1
  30. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +21 -18
  31. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  32. package/dist/commonjs/assertion/impl/sync-parametric.js +31 -46
  33. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  34. package/dist/commonjs/assertion/impl/sync.d.ts +66 -19
  35. package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
  36. package/dist/commonjs/assertion/impl/sync.js +4 -1
  37. package/dist/commonjs/assertion/impl/sync.js.map +1 -1
  38. package/dist/commonjs/bootstrap.d.ts +145 -41
  39. package/dist/commonjs/bootstrap.d.ts.map +1 -1
  40. package/dist/commonjs/bootstrap.js +2 -3
  41. package/dist/commonjs/bootstrap.js.map +1 -1
  42. package/dist/commonjs/constant.js +7 -1
  43. package/dist/commonjs/constant.js.map +1 -1
  44. package/dist/commonjs/error.d.ts +22 -2
  45. package/dist/commonjs/error.d.ts.map +1 -1
  46. package/dist/commonjs/error.js +44 -4
  47. package/dist/commonjs/error.js.map +1 -1
  48. package/dist/commonjs/expect.d.ts.map +1 -1
  49. package/dist/commonjs/expect.js +1 -1
  50. package/dist/commonjs/expect.js.map +1 -1
  51. package/dist/commonjs/guards.d.ts +23 -5
  52. package/dist/commonjs/guards.d.ts.map +1 -1
  53. package/dist/commonjs/guards.js +26 -24
  54. package/dist/commonjs/guards.js.map +1 -1
  55. package/dist/commonjs/index.d.ts +144 -40
  56. package/dist/commonjs/index.d.ts.map +1 -1
  57. package/dist/commonjs/index.js.map +1 -1
  58. package/dist/commonjs/schema.d.ts +18 -8
  59. package/dist/commonjs/schema.d.ts.map +1 -1
  60. package/dist/commonjs/schema.js +21 -11
  61. package/dist/commonjs/schema.js.map +1 -1
  62. package/dist/commonjs/types.d.ts +39 -8
  63. package/dist/commonjs/types.d.ts.map +1 -1
  64. package/dist/commonjs/util.d.ts +66 -50
  65. package/dist/commonjs/util.d.ts.map +1 -1
  66. package/dist/commonjs/util.js +169 -156
  67. package/dist/commonjs/util.js.map +1 -1
  68. package/dist/commonjs/value-to-schema.d.ts +122 -0
  69. package/dist/commonjs/value-to-schema.d.ts.map +1 -0
  70. package/dist/commonjs/value-to-schema.js +309 -0
  71. package/dist/commonjs/value-to-schema.js.map +1 -0
  72. package/dist/esm/assertion/assertion-async.d.ts +2 -1
  73. package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
  74. package/dist/esm/assertion/assertion-async.js +85 -3
  75. package/dist/esm/assertion/assertion-async.js.map +1 -1
  76. package/dist/esm/assertion/assertion-sync.d.ts +1 -1
  77. package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
  78. package/dist/esm/assertion/assertion-sync.js +6 -2
  79. package/dist/esm/assertion/assertion-sync.js.map +1 -1
  80. package/dist/esm/assertion/assertion-types.d.ts +6 -2
  81. package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
  82. package/dist/esm/assertion/assertion.d.ts +1 -1
  83. package/dist/esm/assertion/assertion.d.ts.map +1 -1
  84. package/dist/esm/assertion/assertion.js +1 -14
  85. package/dist/esm/assertion/assertion.js.map +1 -1
  86. package/dist/esm/assertion/impl/async.d.ts +122 -21
  87. package/dist/esm/assertion/impl/async.d.ts.map +1 -1
  88. package/dist/esm/assertion/impl/async.js +111 -87
  89. package/dist/esm/assertion/impl/async.js.map +1 -1
  90. package/dist/esm/assertion/impl/callback.d.ts +104 -0
  91. package/dist/esm/assertion/impl/callback.d.ts.map +1 -0
  92. package/dist/esm/assertion/impl/callback.js +691 -0
  93. package/dist/esm/assertion/impl/callback.js.map +1 -0
  94. package/dist/esm/assertion/impl/index.d.ts +1 -1
  95. package/dist/esm/assertion/impl/index.d.ts.map +1 -1
  96. package/dist/esm/assertion/impl/index.js +1 -1
  97. package/dist/esm/assertion/impl/index.js.map +1 -1
  98. package/dist/esm/assertion/impl/sync-esoteric.js +2 -2
  99. package/dist/esm/assertion/impl/sync-esoteric.js.map +1 -1
  100. package/dist/esm/assertion/impl/sync-parametric.d.ts +21 -18
  101. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  102. package/dist/esm/assertion/impl/sync-parametric.js +31 -46
  103. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  104. package/dist/esm/assertion/impl/sync.d.ts +66 -19
  105. package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
  106. package/dist/esm/assertion/impl/sync.js +3 -1
  107. package/dist/esm/assertion/impl/sync.js.map +1 -1
  108. package/dist/esm/bootstrap.d.ts +145 -41
  109. package/dist/esm/bootstrap.d.ts.map +1 -1
  110. package/dist/esm/bootstrap.js +1 -2
  111. package/dist/esm/bootstrap.js.map +1 -1
  112. package/dist/esm/constant.js +6 -0
  113. package/dist/esm/constant.js.map +1 -1
  114. package/dist/esm/error.d.ts +22 -2
  115. package/dist/esm/error.d.ts.map +1 -1
  116. package/dist/esm/error.js +43 -4
  117. package/dist/esm/error.js.map +1 -1
  118. package/dist/esm/expect.d.ts.map +1 -1
  119. package/dist/esm/expect.js +2 -2
  120. package/dist/esm/expect.js.map +1 -1
  121. package/dist/esm/guards.d.ts +23 -5
  122. package/dist/esm/guards.d.ts.map +1 -1
  123. package/dist/esm/guards.js +22 -21
  124. package/dist/esm/guards.js.map +1 -1
  125. package/dist/esm/index.d.ts +144 -40
  126. package/dist/esm/index.d.ts.map +1 -1
  127. package/dist/esm/index.js.map +1 -1
  128. package/dist/esm/schema.d.ts +18 -8
  129. package/dist/esm/schema.d.ts.map +1 -1
  130. package/dist/esm/schema.js +21 -11
  131. package/dist/esm/schema.js.map +1 -1
  132. package/dist/esm/types.d.ts +39 -8
  133. package/dist/esm/types.d.ts.map +1 -1
  134. package/dist/esm/util.d.ts +66 -50
  135. package/dist/esm/util.d.ts.map +1 -1
  136. package/dist/esm/util.js +153 -154
  137. package/dist/esm/util.js.map +1 -1
  138. package/dist/esm/value-to-schema.d.ts +122 -0
  139. package/dist/esm/value-to-schema.d.ts.map +1 -0
  140. package/dist/esm/value-to-schema.js +305 -0
  141. package/dist/esm/value-to-schema.js.map +1 -0
  142. package/package.json +16 -13
  143. package/src/assertion/assertion-async.ts +113 -3
  144. package/src/assertion/assertion-sync.ts +5 -2
  145. package/src/assertion/assertion-types.ts +14 -4
  146. package/src/assertion/assertion.ts +2 -17
  147. package/src/assertion/impl/async.ts +130 -90
  148. package/src/assertion/impl/callback.ts +882 -0
  149. package/src/assertion/impl/index.ts +1 -1
  150. package/src/assertion/impl/sync-esoteric.ts +2 -2
  151. package/src/assertion/impl/sync-parametric.ts +40 -48
  152. package/src/assertion/impl/sync.ts +3 -0
  153. package/src/bootstrap.ts +1 -2
  154. package/src/constant.ts +8 -0
  155. package/src/error.ts +57 -3
  156. package/src/expect.ts +6 -2
  157. package/src/guards.ts +45 -18
  158. package/src/index.ts +1 -0
  159. package/src/schema.ts +22 -11
  160. package/src/types.ts +40 -8
  161. package/src/util.ts +168 -223
  162. package/src/value-to-schema.ts +464 -0
@@ -1,2 +1,2 @@
1
1
  export { AsyncAssertions } from './async.js';
2
- export { SyncAssertions } from './sync.js';
2
+ export { SyncAssertions as SyncAssertions } from './sync.js';
@@ -1,10 +1,10 @@
1
1
  import { z } from 'zod/v4';
2
2
 
3
- import { NullProtoObjectSchema, PropertyKeySchema } from '../../schema.js';
3
+ import { DictionarySchema, PropertyKeySchema } from '../../schema.js';
4
4
  import { createAssertion } from '../create.js';
5
5
 
6
6
  export const EsotericAssertions = [
7
- createAssertion(['to have a null prototype'], NullProtoObjectSchema),
7
+ createAssertion(['to have a null prototype'], DictionarySchema),
8
8
  createAssertion(
9
9
  [PropertyKeySchema, 'to be an enumerable property of', z.looseObject({})],
10
10
  (subject, obj) =>
@@ -11,7 +11,11 @@ import {
11
11
  StrongSetSchema,
12
12
  WrappedPromiseLikeSchema,
13
13
  } from '../../schema.js';
14
- import { valueToSchema } from '../../util.js';
14
+ import {
15
+ valueToSchema,
16
+ valueToSchemaOptionsForDeepEqual,
17
+ valueToSchemaOptionsForSatisfies,
18
+ } from '../../value-to-schema.js';
15
19
  import { createAssertion } from '../create.js';
16
20
 
17
21
  const trapError = (fn: () => unknown): unknown => {
@@ -304,19 +308,19 @@ export const ParametricAssertions = [
304
308
  }
305
309
  },
306
310
  ),
307
- // @ts-expect-error fix later
308
311
  createAssertion(
309
312
  [
310
313
  z.looseObject({}),
311
314
  ['to deep equal', 'to deeply equal'],
312
315
  z.looseObject({}),
313
316
  ],
314
- (_, expected) => valueToSchema(expected, { strict: true }),
317
+ (_, expected) => valueToSchema(expected, valueToSchemaOptionsForDeepEqual),
315
318
  ),
316
- // @ts-expect-error fix later
317
319
  createAssertion(
318
320
  [ArrayLikeSchema, ['to deep equal', 'to deeply equal'], ArrayLikeSchema],
319
- (_, expected) => valueToSchema(expected, { strict: true }),
321
+ (_, expected) => {
322
+ return valueToSchema(expected, valueToSchemaOptionsForDeepEqual);
323
+ },
320
324
  ),
321
325
  createAssertion([FunctionSchema, 'to throw'], (subject) => {
322
326
  const error = trapError(subject);
@@ -376,10 +380,7 @@ export const ParametricAssertions = [
376
380
  .or(z.coerce.string().regex(param))
377
381
  .safeParse(error).success;
378
382
  } else if (isNonNullObject(param)) {
379
- const schema = valueToSchema(param, {
380
- literalPrimitives: true,
381
- strict: true,
382
- });
383
+ const schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
383
384
  return schema.safeParse(error).success;
384
385
  } else {
385
386
  throw new TypeError(`Invalid parameter schema: ${inspect(param)}`);
@@ -396,6 +397,7 @@ export const ParametricAssertions = [
396
397
  ],
397
398
  (subject, ctor, param) => {
398
399
  const error = trapError(subject);
400
+
399
401
  if (!isA(error, ctor)) {
400
402
  return {
401
403
  actual: error,
@@ -405,47 +407,32 @@ export const ParametricAssertions = [
405
407
  : `Expected function to throw an instance of ${ctor.name}, but it threw a non-object value: ${error as unknown}`,
406
408
  };
407
409
  }
408
-
410
+ let schema: undefined | z.ZodType;
411
+ // TODO: can valueToSchema handle the first two conditional branches?
409
412
  if (isString(param)) {
410
- const result = z
413
+ schema = z
411
414
  .looseObject({
412
- message: z.coerce.string().refine((msg) => msg.includes(param)),
415
+ message: z.coerce.string().pipe(z.literal(param)),
413
416
  })
414
- .or(z.coerce.string().refine((str) => str.includes(param)))
415
- .safeParse(error);
416
- if (!result.success) {
417
- return {
418
- actual: isError(error) ? error.message : String(error),
419
- expected: `error with message containing "${param}"`,
420
- message: `Expected error message to contain "${param}", but got: ${isError(error) ? error.message : String(error)}`,
421
- };
422
- }
417
+ .or(z.coerce.string().pipe(z.literal(param)));
423
418
  } else if (isA(param, RegExp)) {
424
- const result = z
419
+ schema = z
425
420
  .looseObject({
426
421
  message: z.coerce.string().regex(param),
427
422
  })
428
- .or(z.coerce.string().regex(param))
429
- .safeParse(error);
430
- if (!result.success) {
431
- return {
432
- actual: isError(error) ? error.message : String(error),
433
- expected: `error with message matching ${param}`,
434
- message: `Expected error message to match ${param}, but got: ${isError(error) ? error.message : String(error)}`,
435
- };
436
- }
423
+ .or(z.coerce.string().regex(param));
437
424
  } else if (isNonNullObject(param)) {
438
- const schema = valueToSchema(param);
439
- const result = schema.safeParse(error);
440
- if (!result.success) {
441
- return {
442
- actual: error as unknown,
443
- expected: param,
444
- message: `Expected error to match object: ${inspect(param)}, but got: ${inspect(error)}`,
445
- };
446
- }
447
- } else {
448
- throw new TypeError(`Invalid parameter schema: ${inspect(param)}`);
425
+ schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
426
+ }
427
+ if (!schema) {
428
+ throw new TypeError(
429
+ `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
430
+ );
431
+ }
432
+
433
+ const result = schema.safeParse(error);
434
+ if (!result.success) {
435
+ return result.error;
449
436
  }
450
437
  },
451
438
  ),
@@ -455,7 +442,15 @@ export const ParametricAssertions = [
455
442
  ['includes', 'contains', 'to include', 'to contain'],
456
443
  z.string(),
457
444
  ],
458
- (subject, expected) => subject.includes(expected),
445
+ (subject, expected) => {
446
+ if (!subject.includes(expected)) {
447
+ return {
448
+ actual: subject,
449
+ expected: `string including "${expected}"`,
450
+ message: `Expected "${subject}" to include "${expected}"`,
451
+ };
452
+ }
453
+ },
459
454
  ),
460
455
 
461
456
  createAssertion([z.string(), 'to match', RegExpSchema], (subject, regex) =>
@@ -467,14 +462,11 @@ export const ParametricAssertions = [
467
462
  ['to satisfy', 'to be like'],
468
463
  z.looseObject({}),
469
464
  ],
470
- (subject, shape) =>
471
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
472
- valueToSchema(shape) as unknown as z.ZodType<{}, z.core.$loose>,
465
+ (_subject, shape) => valueToSchema(shape, valueToSchemaOptionsForSatisfies),
473
466
  ),
474
467
  createAssertion(
475
468
  [ArrayLikeSchema, ['to satisfy', 'to be like'], ArrayLikeSchema],
476
- (subject, shape) =>
477
- valueToSchema(shape) as unknown as typeof ArrayLikeSchema,
469
+ (_subject, shape) => valueToSchema(shape, valueToSchemaOptionsForSatisfies),
478
470
  ),
479
471
  createAssertion(
480
472
  [FunctionSchema, 'to have arity', z.number().int().nonnegative()],
@@ -9,6 +9,7 @@
9
9
  * @packageDocumentation
10
10
  */
11
11
 
12
+ import { CallbackSyncAssertions } from './callback.js';
12
13
  import { BasicAssertions } from './sync-basic.js';
13
14
  import { CollectionAssertions } from './sync-collection.js';
14
15
  import { EsotericAssertions } from './sync-esoteric.js';
@@ -19,10 +20,12 @@ export const SyncAssertions = [
19
20
  ...BasicAssertions,
20
21
  ...EsotericAssertions,
21
22
  ...ParametricAssertions,
23
+ ...CallbackSyncAssertions,
22
24
  ] as const;
23
25
 
24
26
  export {
25
27
  BasicAssertions,
28
+ CallbackSyncAssertions,
26
29
  CollectionAssertions,
27
30
  EsotericAssertions,
28
31
  ParametricAssertions,
package/src/bootstrap.ts CHANGED
@@ -7,8 +7,7 @@
7
7
  * @packageDocumentation
8
8
  */
9
9
 
10
- import { AsyncAssertions } from './assertion/impl/async.js';
11
- import { SyncAssertions } from './assertion/impl/sync.js';
10
+ import { AsyncAssertions, SyncAssertions } from './assertion/index.js';
12
11
  import { type Expect, type ExpectAsync } from './types.js';
13
12
  import { createUse } from './use.js';
14
13
 
package/src/constant.ts CHANGED
@@ -26,6 +26,14 @@ export const kStringLiteral: unique symbol = Symbol('bupkis:string-literal');
26
26
 
27
27
  export const kBupkisAssertionError: unique symbol = Symbol('bupkis-error');
28
28
 
29
+ /**
30
+ * Symbol used to flag a `FailAssertionError`
31
+ *
32
+ * @internal
33
+ */
34
+ export const kBupkisFailAssertionError: unique symbol =
35
+ Symbol('bupkis-fail-error');
36
+
29
37
  /**
30
38
  * Symbol used to flag a `NegatedAssertionError`
31
39
  *
package/src/error.ts CHANGED
@@ -7,12 +7,15 @@
7
7
  */
8
8
 
9
9
  import { AssertionError as NodeAssertionError } from 'node:assert';
10
+ import { z } from 'zod/v4';
10
11
 
11
12
  import {
12
13
  kBupkisAssertionError,
14
+ kBupkisFailAssertionError,
13
15
  kBupkisNegatedAssertionError,
14
16
  } from './constant.js';
15
17
  import { isA } from './guards.js';
18
+ import { type AssertionParts, type ParsedValues } from './types.js';
16
19
 
17
20
  /**
18
21
  * _BUPKIS_' s custom `AssertionError` class, which is just a thin wrapper
@@ -27,10 +30,39 @@ export class AssertionError extends NodeAssertionError {
27
30
  override name = 'AssertionError';
28
31
 
29
32
  /**
30
- * @param options Options passed to {@link NodeAssertionError}'s constructor
33
+ * Translates a {@link z.ZodError} into an {@link AssertionError} with a
34
+ * human-friendly message.
35
+ *
36
+ * @remarks
37
+ * This does not handle parameterized assertions with more than one parameter
38
+ * too cleanly; it's unclear how a test runner would display the expected
39
+ * values. This will probably need a fix in the future.
40
+ * @param stackStartFn The function to start the stack trace from
41
+ * @param zodError The original `ZodError`
42
+ * @param values Values which caused the error
43
+ * @returns New `AssertionError`
31
44
  */
32
- constructor(options?: ConstructorParameters<typeof NodeAssertionError>[0]) {
33
- super(options);
45
+ static fromZodError<Parts extends AssertionParts>(
46
+ zodError: z.ZodError,
47
+ stackStartFn: (...args: any[]) => any,
48
+ values: ParsedValues<Parts>,
49
+ ): AssertionError {
50
+ const flat = z.flattenError(zodError);
51
+
52
+ let pretty = flat.formErrors.join('; ');
53
+ for (const [keypath, errors] of Object.entries(flat.fieldErrors)) {
54
+ pretty += `; ${keypath}: ${(errors as unknown[]).join('; ')}`;
55
+ }
56
+
57
+ const [actual, ...expected] = values as unknown as [unknown, ...unknown[]];
58
+
59
+ return new AssertionError({
60
+ actual,
61
+ expected: expected.length === 1 ? expected[0] : expected,
62
+ message: `Assertion ${this} failed: ${pretty}`,
63
+ operator: `${this}`,
64
+ stackStartFn,
65
+ });
34
66
  }
35
67
 
36
68
  /**
@@ -47,6 +79,26 @@ export class AssertionError extends NodeAssertionError {
47
79
  }
48
80
  }
49
81
 
82
+ /**
83
+ * Variant of an {@link AssertionError} that is thrown when
84
+ * {@link bupkis!expect.fail} is called.
85
+ */
86
+ export class FailAssertionError extends AssertionError {
87
+ /**
88
+ * @internal
89
+ */
90
+ [kBupkisFailAssertionError] = true;
91
+
92
+ override name = 'FailAssertionError';
93
+
94
+ static isFailAssertionError(err: unknown): err is FailAssertionError {
95
+ return (
96
+ isA(err, FailAssertionError) &&
97
+ Object.hasOwn(err, kBupkisFailAssertionError)
98
+ );
99
+ }
100
+ }
101
+
50
102
  /**
51
103
  * Error type used internally to catch failed negated assertions.
52
104
  *
@@ -55,6 +107,8 @@ export class AssertionError extends NodeAssertionError {
55
107
  export class NegatedAssertionError extends AssertionError {
56
108
  [kBupkisNegatedAssertionError] = true;
57
109
 
110
+ override name = 'NegatedAssertionError';
111
+
58
112
  static isNegatedAssertionError(err: unknown): err is NegatedAssertionError {
59
113
  return (
60
114
  isA(err, AssertionError) &&
package/src/expect.ts CHANGED
@@ -16,7 +16,11 @@ import {
16
16
  type ParsedValues,
17
17
  } from './assertion/assertion-types.js';
18
18
  import { createAssertion, createAsyncAssertion } from './assertion/create.js';
19
- import { AssertionError, NegatedAssertionError } from './error.js';
19
+ import {
20
+ AssertionError,
21
+ FailAssertionError,
22
+ NegatedAssertionError,
23
+ } from './error.js';
20
24
  import { isAssertionFailure, isString } from './guards.js';
21
25
  import {
22
26
  type Expect,
@@ -578,7 +582,7 @@ const detectNegation = (
578
582
  };
579
583
 
580
584
  const fail: FailFn = (reason?: string): never => {
581
- throw new AssertionError({ message: reason });
585
+ throw new FailAssertionError({ message: reason });
582
586
  };
583
587
 
584
588
  /**
package/src/guards.ts CHANGED
@@ -24,7 +24,7 @@ import type {
24
24
  AssertionPart,
25
25
  PhraseLiteralChoice,
26
26
  } from './assertion/assertion-types.js';
27
- import type { Constructor } from './types.js';
27
+ import type { Constructor, ZodTypeMap } from './types.js';
28
28
 
29
29
  /**
30
30
  * Returns `true` if the given value looks like a Zod v4 schema, determined by
@@ -33,28 +33,60 @@ import type { Constructor } from './types.js';
33
33
  * Note: This relies on Zod's internal shape and is intended for runtime
34
34
  * discrimination within this library.
35
35
  *
36
- * @template T
36
+ * @template T - The specific ZodType to check for (based on def.type)
37
37
  * @param value - Value to test
38
38
  * @returns Whether the value is `ZodType`-like
39
39
  */
40
- export const isZodType = (value: unknown): value is z.ZodType =>
41
- !!(
42
- value &&
43
- typeof value === 'object' &&
40
+ export function isZodType<T extends keyof ZodTypeMap>(
41
+ value: unknown,
42
+ type: T,
43
+ ): value is ZodTypeMap[T];
44
+ /**
45
+ * Returns `true` if the given value looks like a Zod v4 schema, determined by
46
+ * the presence of an internal {@link z.core.$ZodTypeDef} field.
47
+ *
48
+ * Note: This relies on Zod's internal shape and is intended for runtime
49
+ * discrimination within this library.
50
+ *
51
+ * @param value - Value to test
52
+ * @returns Whether the value is `ZodType`-like
53
+ */
54
+ export function isZodType(value: unknown): value is z.ZodType;
55
+ export function isZodType<T extends keyof ZodTypeMap>(
56
+ value: unknown,
57
+ type?: T,
58
+ ): value is T extends keyof ZodTypeMap ? ZodTypeMap[T] : z.ZodType {
59
+ const isValid =
60
+ isObject(value) &&
44
61
  'def' in value &&
45
- value.def &&
62
+ !!value.def &&
46
63
  typeof value.def === 'object' &&
47
- 'type' in value.def
48
- );
64
+ 'type' in value.def;
65
+
66
+ if (!isValid) return false;
67
+ if (type === undefined) return true;
68
+
69
+ return (value as z.ZodType).def.type === type;
70
+ }
71
+
72
+ /**
73
+ * Type guard for a plain object.
74
+ *
75
+ * @param value Value to test
76
+ * @returns `true` if the value is a plain object, `false` otherwise
77
+ */
78
+ export const isObject = (value: unknown): value is NonNullable<object> => {
79
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
80
+ };
49
81
 
50
82
  /**
51
- * Returns true if the given value is a {@link z.ZodPromise} schema.
83
+ * Returns `true` if the given value is a {@link z.ZodPromise} schema.
52
84
  *
53
85
  * @param value - Value to test
54
86
  * @returns `true` if the value is a `ZodPromise` schema; `false` otherwise
55
87
  */
56
88
  export const isZodPromise = (value: unknown): value is z.ZodPromise =>
57
- isZodType(value) && value.def.type === 'promise';
89
+ isZodType(value, 'promise');
58
90
 
59
91
  /**
60
92
  * Checks if a value is "promise-like", meaning it is a "thenable" object.
@@ -63,12 +95,7 @@ export const isZodPromise = (value: unknown): value is z.ZodPromise =>
63
95
  * @returns `true` if the value is promise-like, `false` otherwise
64
96
  */
65
97
  export const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>
66
- !!(
67
- value &&
68
- typeof value === 'object' &&
69
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
70
- typeof (value as any).then === 'function'
71
- );
98
+ isObject(value) && 'then' in value && isFunction(value.then);
72
99
 
73
100
  /**
74
101
  * Returns `true` if the given value is a constructable function (i.e., a
@@ -85,7 +112,7 @@ export const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>
85
112
  * @param fn - Function to test
86
113
  * @returns Whether the function is constructable
87
114
  */
88
- export const isConstructable = (fn: unknown): fn is Constructor => {
115
+ export const isConstructible = (fn: unknown): fn is Constructor => {
89
116
  if (fn === Symbol || fn === BigInt) {
90
117
  return false;
91
118
  }
package/src/index.ts CHANGED
@@ -52,6 +52,7 @@ export type {
52
52
  ExpectAsync,
53
53
  FailFn,
54
54
  UseFn,
55
+ ZodTypeMap,
55
56
  } from './types.js';
56
57
  export { createAssertion, createAsyncAssertion, fail, use };
57
58
  const {
package/src/schema.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * This module provides reusable Zod schemas for validating constructors,
5
5
  * functions, property keys, promises, and other common JavaScript types used
6
- * throughout the assertion system. These tend to work around the impedence
6
+ * throughout the assertion system. These tend to work around the impedance
7
7
  * mismatch between **BUPKIS** and Zod.
8
8
  *
9
9
  * These are used internally, but consumers may also find them useful.
@@ -36,7 +36,7 @@ import { z } from 'zod/v4';
36
36
 
37
37
  import {
38
38
  isA,
39
- isConstructable,
39
+ isConstructible,
40
40
  isFunction,
41
41
  isNonNullObject,
42
42
  isPromiseLike,
@@ -49,7 +49,7 @@ import { type Constructor, type MutableOrReadonly } from './types.js';
49
49
  *
50
50
  * This schema validates values that can be used as constructors, including ES6
51
51
  * classes, traditional constructor functions, and built-in constructors. It
52
- * uses the {@link isConstructable} guard function to determine if a value can be
52
+ * uses the {@link isConstructible} guard function to determine if a value can be
53
53
  * invoked with the `new` operator to create object instances.
54
54
  *
55
55
  * @privateRemarks
@@ -73,7 +73,7 @@ import { type Constructor, type MutableOrReadonly } from './types.js';
73
73
  */
74
74
 
75
75
  export const ClassSchema = z
76
- .custom<Constructor>(isConstructable)
76
+ .custom<Constructor>(isConstructible)
77
77
  .register(BupkisRegistry, { name: 'ClassSchema' })
78
78
  .describe('Class / Constructor');
79
79
 
@@ -242,9 +242,12 @@ export const StrongSetSchema = z
242
242
  * `Object.prototype`, making them useful as pure data containers or
243
243
  * dictionaries.
244
244
  *
245
- * @remarks
245
+ * @privateRemarks
246
246
  * The schema is registered in the `BupkisRegistry` with the name
247
247
  * `ObjectWithNullPrototype` for later reference and type checking purposes.
248
+ *
249
+ * Changing this to be a `ZodRecord` would be nice, but that would end up
250
+ * blasting away the original object's prototype.
248
251
  * @example
249
252
  *
250
253
  * ```typescript
@@ -260,14 +263,22 @@ export const StrongSetSchema = z
260
263
  * ```
261
264
  *
262
265
  * @group Schema
266
+ * @see Aliases: {@link NullProtoObjectSchema}, {@link DictionarySchema}
263
267
  */
264
- export const NullProtoObjectSchema = z
268
+ export const DictionarySchema = z
265
269
  .custom<Record<PropertyKey, unknown>>(
266
270
  (value) => isNonNullObject(value) && Object.getPrototypeOf(value) === null,
267
271
  )
268
272
  .describe('Object with null prototype')
269
273
  .register(BupkisRegistry, { name: 'ObjectWithNullPrototype' });
270
274
 
275
+ /**
276
+ * {@inheritDoc DictionarySchema}
277
+ *
278
+ * @group Schema
279
+ */
280
+ export const NullProtoObjectSchema = DictionarySchema;
281
+
271
282
  /**
272
283
  * A Zod schema that validates functions declared with the `async` keyword.
273
284
  *
@@ -276,7 +287,7 @@ export const NullProtoObjectSchema = z
276
287
  * function's internal `[[ToString]]` representation to distinguish async
277
288
  * functions from regular functions that might return Promises.
278
289
  *
279
- * @remarks
290
+ * @privateRemarks
280
291
  * The schema is registered in the `BupkisRegistry` with the name
281
292
  * `AsyncFunctionSchema` for later reference and type checking purposes. This
282
293
  * schema cannot reliably detect functions that return Promises but are not
@@ -318,7 +329,7 @@ export const AsyncFunctionSchema = FunctionSchema.refine(
318
329
  * if it converts to `true` when evaluated in a boolean context - essentially
319
330
  * any value that is not one of the eight falsy values.
320
331
  *
321
- * @remarks
332
+ * @privateRemarks
322
333
  * The schema is registered in the `BupkisRegistry` with the name `Truthy` and
323
334
  * indicates that it accepts anything as valid input for evaluation.
324
335
  * @example
@@ -354,7 +365,7 @@ export const TruthySchema = z
354
365
  * in JavaScript are: `false`, `0`, `-0`, `0n`, `""` (empty string), `null`,
355
366
  * `undefined`, and `NaN`.
356
367
  *
357
- * @remarks
368
+ * @privateRemarks
358
369
  * The schema is registered in the `BupkisRegistry` with the name `Falsy` and
359
370
  * indicates that it accepts anything as valid input for evaluation.
360
371
  * @example
@@ -392,7 +403,7 @@ export const FalsySchema = z
392
403
  * distinguishing them from objects and functions which are non-primitive
393
404
  * reference types.
394
405
  *
395
- * @remarks
406
+ * @privateRemarks
396
407
  * The schema is registered in the `BupkisRegistry` with the name `Primitive`
397
408
  * and indicates that it accepts primitive values as valid input.
398
409
  * @example
@@ -474,7 +485,7 @@ export const ArrayLikeSchema = z
474
485
  * It ensures the validated value is a proper regular expression object with all
475
486
  * associated methods and properties.
476
487
  *
477
- * @remarks
488
+ * @privateRemarks
478
489
  * The schema is registered in the `BupkisRegistry` with the name `RegExp` for
479
490
  * later reference and type checking purposes.
480
491
  * @example
package/src/types.ts CHANGED
@@ -269,14 +269,7 @@ export interface CreateAsyncAssertionFn {
269
269
  parts: Parts,
270
270
  impl: Impl,
271
271
  ): AssertionFunctionAsync<Parts, Impl, Slots>;
272
- } /**
273
- * @template BaseSyncAssertions Base set of synchronous
274
- * {@link Assertion | Assertions}; will be the builtin sync assertions, at
275
- * minimum)
276
- * @template BaseAsyncAssertions Base set of asynchronous
277
- * {@link Assertion | Assertions}; will be the builtin async assertions, at
278
- * minimum)
279
- */
272
+ }
280
273
 
281
274
  /**
282
275
  * The main synchronous assertion function.
@@ -684,3 +677,42 @@ export interface UseFn<
684
677
  ExtendedAsyncAssertions
685
678
  >;
686
679
  }
680
+
681
+ /**
682
+ * Maps Zod `def.type` strings to their corresponding ZodType classes.
683
+ *
684
+ * This allows for type-safe discrimination of ZodTypes based on their internal
685
+ * `def.type` property in Zod v4.
686
+ */
687
+ export interface ZodTypeMap {
688
+ any: z.ZodAny;
689
+ array: z.ZodArray<any>;
690
+ bigint: z.ZodBigInt;
691
+ boolean: z.ZodBoolean;
692
+ catch: z.ZodCatch<any>;
693
+ date: z.ZodDate;
694
+ default: z.ZodDefault<any>;
695
+ enum: z.ZodEnum<any>;
696
+ function: z.ZodFunction<any, any>;
697
+ lazy: z.ZodLazy<any>;
698
+ literal: z.ZodLiteral<any>;
699
+ map: z.ZodMap<any, any>;
700
+ never: z.ZodNever;
701
+ null: z.ZodNull;
702
+ nullable: z.ZodNullable<any>;
703
+ number: z.ZodNumber;
704
+ object: z.ZodObject<any>;
705
+ optional: z.ZodOptional<any>;
706
+ pipe: z.ZodPipe<any, any>;
707
+ promise: z.ZodPromise<any>;
708
+ readonly: z.ZodReadonly<any>;
709
+ record: z.ZodRecord<any, any>;
710
+ set: z.ZodSet<any>;
711
+ string: z.ZodString;
712
+ symbol: z.ZodSymbol;
713
+ tuple: z.ZodTuple<any>;
714
+ undefined: z.ZodUndefined;
715
+ union: z.ZodUnion<any>;
716
+ unknown: z.ZodUnknown;
717
+ void: z.ZodVoid;
718
+ }