bupkis 0.7.2 → 0.9.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 (215) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +19 -2
  3. package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
  4. package/dist/commonjs/assertion/assertion-async.js +37 -7
  5. package/dist/commonjs/assertion/assertion-async.js.map +1 -1
  6. package/dist/commonjs/assertion/assertion-sync.d.ts.map +1 -1
  7. package/dist/commonjs/assertion/assertion-sync.js +32 -8
  8. package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
  9. package/dist/commonjs/assertion/assertion-types.d.ts +37 -31
  10. package/dist/commonjs/assertion/assertion-types.d.ts.map +1 -1
  11. package/dist/commonjs/assertion/assertion-types.js +0 -32
  12. package/dist/commonjs/assertion/assertion-types.js.map +1 -1
  13. package/dist/commonjs/assertion/assertion.d.ts +3 -21
  14. package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
  15. package/dist/commonjs/assertion/assertion.js +42 -27
  16. package/dist/commonjs/assertion/assertion.js.map +1 -1
  17. package/dist/commonjs/assertion/create.d.ts +2 -0
  18. package/dist/commonjs/assertion/create.d.ts.map +1 -1
  19. package/dist/commonjs/assertion/create.js +38 -42
  20. package/dist/commonjs/assertion/create.js.map +1 -1
  21. package/dist/commonjs/assertion/impl/assertion-util.d.ts +16 -4
  22. package/dist/commonjs/assertion/impl/assertion-util.d.ts.map +1 -1
  23. package/dist/commonjs/assertion/impl/assertion-util.js +20 -15
  24. package/dist/commonjs/assertion/impl/assertion-util.js.map +1 -1
  25. package/dist/commonjs/assertion/impl/async-parametric.d.ts +63 -11
  26. package/dist/commonjs/assertion/impl/async-parametric.d.ts.map +1 -1
  27. package/dist/commonjs/assertion/impl/async-parametric.js +89 -52
  28. package/dist/commonjs/assertion/impl/async-parametric.js.map +1 -1
  29. package/dist/commonjs/assertion/impl/async.d.ts +116 -12
  30. package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
  31. package/dist/commonjs/assertion/impl/async.js +1 -1
  32. package/dist/commonjs/assertion/impl/sync-basic.d.ts.map +1 -1
  33. package/dist/commonjs/assertion/impl/sync-basic.js +6 -4
  34. package/dist/commonjs/assertion/impl/sync-basic.js.map +1 -1
  35. package/dist/commonjs/assertion/impl/sync-collection.d.ts.map +1 -1
  36. package/dist/commonjs/assertion/impl/sync-collection.js +24 -14
  37. package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
  38. package/dist/commonjs/assertion/impl/sync-esoteric.d.ts +1 -5
  39. package/dist/commonjs/assertion/impl/sync-esoteric.d.ts.map +1 -1
  40. package/dist/commonjs/assertion/impl/sync-esoteric.js +11 -13
  41. package/dist/commonjs/assertion/impl/sync-esoteric.js.map +1 -1
  42. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +27 -7
  43. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  44. package/dist/commonjs/assertion/impl/sync-parametric.js +75 -51
  45. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  46. package/dist/commonjs/assertion/impl/sync.d.ts +54 -22
  47. package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
  48. package/dist/commonjs/assertion/impl/sync.js +1 -1
  49. package/dist/commonjs/assertion/impl/sync.js.map +1 -1
  50. package/dist/commonjs/assertion/index.d.ts +1 -1
  51. package/dist/commonjs/assertion/index.d.ts.map +1 -1
  52. package/dist/commonjs/assertion/index.js +0 -1
  53. package/dist/commonjs/assertion/index.js.map +1 -1
  54. package/dist/commonjs/assertion/slotify.d.ts +1 -13
  55. package/dist/commonjs/assertion/slotify.d.ts.map +1 -1
  56. package/dist/commonjs/assertion/slotify.js +49 -16
  57. package/dist/commonjs/assertion/slotify.js.map +1 -1
  58. package/dist/commonjs/bootstrap.d.ts +85 -17
  59. package/dist/commonjs/bootstrap.d.ts.map +1 -1
  60. package/dist/commonjs/bootstrap.js +1 -0
  61. package/dist/commonjs/bootstrap.js.map +1 -1
  62. package/dist/commonjs/diff.d.ts +51 -0
  63. package/dist/commonjs/diff.d.ts.map +1 -0
  64. package/dist/commonjs/diff.js +279 -0
  65. package/dist/commonjs/diff.js.map +1 -0
  66. package/dist/commonjs/error.d.ts +37 -18
  67. package/dist/commonjs/error.d.ts.map +1 -1
  68. package/dist/commonjs/error.js +44 -30
  69. package/dist/commonjs/error.js.map +1 -1
  70. package/dist/commonjs/expect.d.ts.map +1 -1
  71. package/dist/commonjs/expect.js +131 -78
  72. package/dist/commonjs/expect.js.map +1 -1
  73. package/dist/commonjs/guards.d.ts +24 -10
  74. package/dist/commonjs/guards.d.ts.map +1 -1
  75. package/dist/commonjs/guards.js +56 -39
  76. package/dist/commonjs/guards.js.map +1 -1
  77. package/dist/commonjs/index.d.ts +85 -17
  78. package/dist/commonjs/index.d.ts.map +1 -1
  79. package/dist/commonjs/internal-schema.d.ts +25 -0
  80. package/dist/commonjs/internal-schema.d.ts.map +1 -0
  81. package/dist/commonjs/internal-schema.js +209 -0
  82. package/dist/commonjs/internal-schema.js.map +1 -0
  83. package/dist/commonjs/schema.d.ts.map +1 -1
  84. package/dist/commonjs/schema.js +3 -2
  85. package/dist/commonjs/schema.js.map +1 -1
  86. package/dist/commonjs/use.js +22 -8
  87. package/dist/commonjs/use.js.map +1 -1
  88. package/dist/commonjs/util.d.ts +1 -0
  89. package/dist/commonjs/util.d.ts.map +1 -1
  90. package/dist/commonjs/util.js +14 -10
  91. package/dist/commonjs/util.js.map +1 -1
  92. package/dist/commonjs/value-to-schema.d.ts +1 -0
  93. package/dist/commonjs/value-to-schema.d.ts.map +1 -1
  94. package/dist/commonjs/value-to-schema.js +19 -12
  95. package/dist/commonjs/value-to-schema.js.map +1 -1
  96. package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
  97. package/dist/esm/assertion/assertion-async.js +37 -7
  98. package/dist/esm/assertion/assertion-async.js.map +1 -1
  99. package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
  100. package/dist/esm/assertion/assertion-sync.js +32 -8
  101. package/dist/esm/assertion/assertion-sync.js.map +1 -1
  102. package/dist/esm/assertion/assertion-types.d.ts +37 -31
  103. package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
  104. package/dist/esm/assertion/assertion-types.js +1 -31
  105. package/dist/esm/assertion/assertion-types.js.map +1 -1
  106. package/dist/esm/assertion/assertion.d.ts +3 -21
  107. package/dist/esm/assertion/assertion.d.ts.map +1 -1
  108. package/dist/esm/assertion/assertion.js +42 -27
  109. package/dist/esm/assertion/assertion.js.map +1 -1
  110. package/dist/esm/assertion/create.d.ts +2 -0
  111. package/dist/esm/assertion/create.d.ts.map +1 -1
  112. package/dist/esm/assertion/create.js +37 -41
  113. package/dist/esm/assertion/create.js.map +1 -1
  114. package/dist/esm/assertion/impl/assertion-util.d.ts +16 -4
  115. package/dist/esm/assertion/impl/assertion-util.d.ts.map +1 -1
  116. package/dist/esm/assertion/impl/assertion-util.js +20 -15
  117. package/dist/esm/assertion/impl/assertion-util.js.map +1 -1
  118. package/dist/esm/assertion/impl/async-parametric.d.ts +63 -11
  119. package/dist/esm/assertion/impl/async-parametric.d.ts.map +1 -1
  120. package/dist/esm/assertion/impl/async-parametric.js +89 -52
  121. package/dist/esm/assertion/impl/async-parametric.js.map +1 -1
  122. package/dist/esm/assertion/impl/async.d.ts +116 -12
  123. package/dist/esm/assertion/impl/async.d.ts.map +1 -1
  124. package/dist/esm/assertion/impl/async.js +2 -2
  125. package/dist/esm/assertion/impl/async.js.map +1 -1
  126. package/dist/esm/assertion/impl/sync-basic.d.ts.map +1 -1
  127. package/dist/esm/assertion/impl/sync-basic.js +6 -4
  128. package/dist/esm/assertion/impl/sync-basic.js.map +1 -1
  129. package/dist/esm/assertion/impl/sync-collection.d.ts.map +1 -1
  130. package/dist/esm/assertion/impl/sync-collection.js +24 -14
  131. package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
  132. package/dist/esm/assertion/impl/sync-esoteric.d.ts +1 -5
  133. package/dist/esm/assertion/impl/sync-esoteric.d.ts.map +1 -1
  134. package/dist/esm/assertion/impl/sync-esoteric.js +11 -13
  135. package/dist/esm/assertion/impl/sync-esoteric.js.map +1 -1
  136. package/dist/esm/assertion/impl/sync-parametric.d.ts +27 -7
  137. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  138. package/dist/esm/assertion/impl/sync-parametric.js +75 -51
  139. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  140. package/dist/esm/assertion/impl/sync.d.ts +54 -22
  141. package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
  142. package/dist/esm/assertion/impl/sync.js +2 -2
  143. package/dist/esm/assertion/impl/sync.js.map +1 -1
  144. package/dist/esm/assertion/index.d.ts +1 -1
  145. package/dist/esm/assertion/index.d.ts.map +1 -1
  146. package/dist/esm/assertion/index.js +0 -1
  147. package/dist/esm/assertion/index.js.map +1 -1
  148. package/dist/esm/assertion/slotify.d.ts +1 -13
  149. package/dist/esm/assertion/slotify.d.ts.map +1 -1
  150. package/dist/esm/assertion/slotify.js +50 -17
  151. package/dist/esm/assertion/slotify.js.map +1 -1
  152. package/dist/esm/bootstrap.d.ts +85 -17
  153. package/dist/esm/bootstrap.d.ts.map +1 -1
  154. package/dist/esm/bootstrap.js +1 -0
  155. package/dist/esm/bootstrap.js.map +1 -1
  156. package/dist/esm/diff.d.ts +51 -0
  157. package/dist/esm/diff.d.ts.map +1 -0
  158. package/dist/esm/diff.js +273 -0
  159. package/dist/esm/diff.js.map +1 -0
  160. package/dist/esm/error.d.ts +37 -18
  161. package/dist/esm/error.d.ts.map +1 -1
  162. package/dist/esm/error.js +41 -27
  163. package/dist/esm/error.js.map +1 -1
  164. package/dist/esm/expect.d.ts.map +1 -1
  165. package/dist/esm/expect.js +133 -80
  166. package/dist/esm/expect.js.map +1 -1
  167. package/dist/esm/guards.d.ts +24 -10
  168. package/dist/esm/guards.d.ts.map +1 -1
  169. package/dist/esm/guards.js +52 -36
  170. package/dist/esm/guards.js.map +1 -1
  171. package/dist/esm/index.d.ts +85 -17
  172. package/dist/esm/index.d.ts.map +1 -1
  173. package/dist/esm/internal-schema.d.ts +25 -0
  174. package/dist/esm/internal-schema.d.ts.map +1 -0
  175. package/dist/esm/internal-schema.js +203 -0
  176. package/dist/esm/internal-schema.js.map +1 -0
  177. package/dist/esm/schema.d.ts.map +1 -1
  178. package/dist/esm/schema.js +3 -2
  179. package/dist/esm/schema.js.map +1 -1
  180. package/dist/esm/use.js +19 -6
  181. package/dist/esm/use.js.map +1 -1
  182. package/dist/esm/util.d.ts +1 -0
  183. package/dist/esm/util.d.ts.map +1 -1
  184. package/dist/esm/util.js +14 -10
  185. package/dist/esm/util.js.map +1 -1
  186. package/dist/esm/value-to-schema.d.ts +1 -0
  187. package/dist/esm/value-to-schema.d.ts.map +1 -1
  188. package/dist/esm/value-to-schema.js +20 -13
  189. package/dist/esm/value-to-schema.js.map +1 -1
  190. package/package.json +29 -11
  191. package/src/assertion/assertion-async.ts +42 -14
  192. package/src/assertion/assertion-sync.ts +40 -17
  193. package/src/assertion/assertion-types.ts +55 -45
  194. package/src/assertion/assertion.ts +49 -32
  195. package/src/assertion/create.ts +46 -65
  196. package/src/assertion/impl/assertion-util.ts +31 -18
  197. package/src/assertion/impl/async-parametric.ts +93 -52
  198. package/src/assertion/impl/async.ts +2 -2
  199. package/src/assertion/impl/sync-basic.ts +7 -4
  200. package/src/assertion/impl/sync-collection.ts +34 -14
  201. package/src/assertion/impl/sync-esoteric.ts +17 -13
  202. package/src/assertion/impl/sync-parametric.ts +79 -52
  203. package/src/assertion/impl/sync.ts +2 -2
  204. package/src/assertion/index.ts +1 -1
  205. package/src/assertion/slotify.ts +67 -21
  206. package/src/bootstrap.ts +1 -0
  207. package/src/diff.ts +343 -0
  208. package/src/error.ts +66 -31
  209. package/src/expect.ts +195 -129
  210. package/src/guards.ts +74 -48
  211. package/src/internal-schema.ts +246 -0
  212. package/src/schema.ts +4 -2
  213. package/src/use.ts +21 -7
  214. package/src/util.ts +15 -12
  215. package/src/value-to-schema.ts +21 -13
@@ -16,7 +16,7 @@
16
16
  import { inspect } from 'node:util';
17
17
  import { z } from 'zod/v4';
18
18
 
19
- import { BupkisError, InvalidSchemaError } from '../../error.js';
19
+ import { BupkisError, InvalidObjectSchemaError } from '../../error.js';
20
20
  import { isA, isError, isNonNullObject, isString } from '../../guards.js';
21
21
  import {
22
22
  ArrayLikeSchema,
@@ -34,10 +34,13 @@ import {
34
34
  import { createAssertion } from '../create.js';
35
35
  import { trapError } from './assertion-util.js';
36
36
 
37
+ const { freeze } = Object;
38
+ const { abs } = Math;
39
+
37
40
  /**
38
41
  * For {@link typeOfAssertion}
39
42
  */
40
- const knownTypes = Object.freeze(
43
+ const knownTypes = freeze(
41
44
  new Set([
42
45
  'string',
43
46
  'number',
@@ -271,7 +274,7 @@ export const numberWithinRangeAssertion = createAssertion(
271
274
  export const numberCloseToAssertion = createAssertion(
272
275
  [z.number(), 'to be close to', z.number(), z.number().optional()],
273
276
  (subject, expected, tolerance = 1e-9) => {
274
- const diff = Math.abs(subject - expected);
277
+ const diff = abs(subject - expected);
275
278
  if (diff > tolerance) {
276
279
  return {
277
280
  actual: subject,
@@ -634,14 +637,19 @@ export const arrayDeepEqualAssertion = createAssertion(
634
637
  export const functionThrowsAssertion = createAssertion(
635
638
  [FunctionSchema, 'to throw'],
636
639
  (subject) => {
637
- const error = trapError(subject);
638
- if (!error) {
640
+ const { error, result } = trapError(subject);
641
+ if (error === undefined) {
639
642
  return {
640
- actual: error,
643
+ actual: result,
641
644
  message: `Expected function to throw, but it did not`,
642
645
  };
643
646
  }
644
647
  },
648
+ {
649
+ anchor: 'function-to-throw-any',
650
+ category: 'function',
651
+ redirect: 'to-throw',
652
+ },
645
653
  );
646
654
 
647
655
  /**
@@ -671,21 +679,27 @@ export const functionThrowsAssertion = createAssertion(
671
679
  export const functionThrowsTypeAssertion = createAssertion(
672
680
  [FunctionSchema, ['to throw a', 'to throw an'], ConstructibleSchema],
673
681
  (subject, ctor) => {
674
- const error = trapError(subject);
675
- if (!error) {
676
- return false;
682
+ const { error, result } = trapError(subject);
683
+ if (error === undefined) {
684
+ return {
685
+ actual: result,
686
+ expected: `to throw instance of ${ctor.name}`,
687
+ message: 'Expected function to throw, but it did not',
688
+ };
677
689
  }
678
- if (!(error instanceof ctor)) {
679
- let message: string;
680
- if (isError(error)) {
681
- message = `Expected function to throw an instance of ${ctor.name}, but it threw ${error.constructor.name}`;
682
- } else {
683
- message = `Expected function to throw an instance of ${ctor.name}, but it threw a non-object value: ${error as unknown}`;
690
+ if (!isA(error, ctor)) {
691
+ if (isNonNullObject(error)) {
692
+ const err = error as object;
693
+ return {
694
+ actual: err.constructor.name,
695
+ expected: ctor.name,
696
+ message: `Expected function to throw with an instance of ${ctor.name}, but it threw with a ${err.constructor.name}`,
697
+ };
684
698
  }
685
699
  return {
686
- actual: error,
687
- expected: ctor,
688
- message,
700
+ actual: typeof error,
701
+ expected: ctor.name,
702
+ message: `Expected function to throw with an instance of ${ctor.name}, but it threw a value of type ${typeof error}: ${inspect(error)}`,
689
703
  };
690
704
  }
691
705
  },
@@ -723,33 +737,41 @@ export const functionThrowsTypeAssertion = createAssertion(
723
737
  *
724
738
  * @group Parametric Assertions (Sync)
725
739
  */
726
- export const functionThrowsMatchingAssertion = createAssertion(
740
+ export const functionThrowsSatisfyingAssertion = createAssertion(
727
741
  [FunctionSchema, ['to throw', 'to throw error satisfying'], z.any()],
728
742
  (subject, param) => {
729
- const error = trapError(subject);
730
- if (!error) {
731
- return false;
743
+ const { error, result } = trapError(subject);
744
+ if (error === undefined) {
745
+ return {
746
+ actual: result,
747
+ expected: 'to throw',
748
+ message: 'Expected function to throw, but it did not',
749
+ };
732
750
  }
733
751
 
734
752
  if (isString(param)) {
735
- return z
736
- .looseObject({
737
- message: z.coerce.string().pipe(z.literal(param)),
738
- })
739
- .or(z.coerce.string().pipe(z.literal(param)))
740
- .safeParse(error).success;
753
+ return {
754
+ schema: z
755
+ .looseObject({
756
+ message: z.coerce.string().pipe(z.literal(param)),
757
+ })
758
+ .or(z.coerce.string().pipe(z.literal(param))),
759
+ subject: error,
760
+ };
741
761
  } else if (isA(param, RegExp)) {
742
- return z
743
- .looseObject({
744
- message: z.coerce.string().regex(param),
745
- })
746
- .or(z.coerce.string().regex(param))
747
- .safeParse(error).success;
762
+ return {
763
+ schema: z
764
+ .looseObject({
765
+ message: z.coerce.string().regex(param),
766
+ })
767
+ .or(z.coerce.string().regex(param)),
768
+ subject: error,
769
+ };
748
770
  } else if (isNonNullObject(param)) {
749
771
  const schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
750
- return schema.safeParse(error).success;
772
+ return { schema, subject: error };
751
773
  } else {
752
- throw new InvalidSchemaError(
774
+ throw new InvalidObjectSchemaError(
753
775
  `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
754
776
  { schema: param },
755
777
  );
@@ -795,7 +817,14 @@ export const functionThrowsTypeSatisfyingAssertion = createAssertion(
795
817
  z.any(),
796
818
  ],
797
819
  (subject, ctor, param) => {
798
- const error = trapError(subject);
820
+ const { error, result } = trapError(subject);
821
+ if (error === undefined) {
822
+ return {
823
+ actual: result,
824
+ expected: `to throw instance of ${ctor.name}`,
825
+ message: 'Expected function to throw, but it did not',
826
+ };
827
+ }
799
828
  if (!isA(error, ctor)) {
800
829
  return {
801
830
  actual: error,
@@ -808,31 +837,29 @@ export const functionThrowsTypeSatisfyingAssertion = createAssertion(
808
837
  let schema: undefined | z.ZodType;
809
838
  // TODO: can valueToSchema handle the first two conditional branches?
810
839
  if (isString(param)) {
811
- schema = z
812
- .looseObject({
813
- message: z.coerce.string().pipe(z.literal(param)),
814
- })
815
- .or(z.coerce.string().pipe(z.literal(param)));
840
+ schema = z.looseObject({
841
+ message: z.coerce.string().pipe(z.literal(param)),
842
+ });
843
+ // .or(z.coerce.string().pipe(z.literal(param)));
816
844
  } else if (isA(param, RegExp)) {
817
- schema = z
818
- .looseObject({
819
- message: z.coerce.string().regex(param),
820
- })
821
- .or(z.coerce.string().regex(param));
845
+ schema = z.looseObject({
846
+ message: z.coerce.string().regex(param),
847
+ });
848
+ // .or(z.coerce.string().regex(param));
822
849
  } else if (isNonNullObject(param)) {
823
850
  schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
824
851
  }
825
852
  if (!schema) {
826
- throw new InvalidSchemaError(
853
+ throw new InvalidObjectSchemaError(
827
854
  `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
828
855
  { schema: param },
829
856
  );
830
857
  }
831
858
 
832
- const result = schema.safeParse(error);
833
- if (!result.success) {
834
- return result.error;
835
- }
859
+ return {
860
+ schema,
861
+ subject: error,
862
+ };
836
863
  },
837
864
  );
838
865
 
@@ -100,7 +100,7 @@ import {
100
100
  errorMessageMatchingAssertion,
101
101
  functionArityAssertion,
102
102
  functionThrowsAssertion,
103
- functionThrowsMatchingAssertion,
103
+ functionThrowsSatisfyingAssertion,
104
104
  functionThrowsTypeAssertion,
105
105
  functionThrowsTypeSatisfyingAssertion,
106
106
  instanceOfAssertion,
@@ -215,7 +215,7 @@ export const SyncParametricAssertions = [
215
215
  arrayDeepEqualAssertion,
216
216
  functionThrowsAssertion,
217
217
  functionThrowsTypeAssertion,
218
- functionThrowsMatchingAssertion,
218
+ functionThrowsSatisfyingAssertion,
219
219
  functionThrowsTypeSatisfyingAssertion,
220
220
  stringIncludesAssertion,
221
221
  stringMatchesAssertion,
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  export { BupkisRegistry } from '../metadata.js';
14
- export * from './assertion-types.js';
14
+ export type * from './assertion-types.js';
15
15
  export { BupkisAssertion } from './assertion.js';
16
16
  export { createAssertion, createAsyncAssertion } from './create.js';
17
17
  export * from './impl/async.js';
@@ -11,11 +11,17 @@
11
11
  import { inspect } from 'util';
12
12
  import { z } from 'zod/v4';
13
13
 
14
- import type { AssertionParts, AssertionSlots } from './assertion-types.js';
14
+ import type {
15
+ AssertionParts,
16
+ AssertionSlots,
17
+ PhraseLiteral,
18
+ PhraseLiteralChoice,
19
+ } from './assertion-types.js';
15
20
 
16
21
  import { kStringLiteral } from '../constant.js';
17
22
  import { AssertionImplementationError } from '../error.js';
18
23
  import {
24
+ isPhrase,
19
25
  isPhraseLiteral,
20
26
  isPhraseLiteralChoice,
21
27
  isZodType,
@@ -30,35 +36,29 @@ import { BupkisRegistry } from '../metadata.js';
30
36
  * processing string literals and Zod schemas, registering metadata for runtime
31
37
  * introspection, and handling validation constraints such as preventing "not "
32
38
  * prefixes in string literal parts.
39
+ * @function
33
40
  * @param parts Assertion parts
34
41
  * @returns Slots
42
+ * @internal
35
43
  */
36
44
  export const slotify = <const Parts extends AssertionParts>(
37
45
  parts: Parts,
38
46
  ): AssertionSlots<Parts> =>
39
47
  parts.flatMap((part, index) => {
40
48
  const result: z.ZodType[] = [];
41
- if (index === 0 && (isPhraseLiteralChoice(part) || isPhraseLiteral(part))) {
49
+ if (index === 0 && isPhrase(part)) {
42
50
  result.push(z.unknown().describe('subject'));
43
51
  }
44
52
 
45
53
  if (isPhraseLiteralChoice(part)) {
46
54
  if (part.some((p) => p.startsWith('not '))) {
47
55
  throw new AssertionImplementationError(
48
- `PhraseLiteralChoice at parts[${index}] must not include phrases starting with "not ": ${inspect(
56
+ `PhraseLiteralChoice at parts[${index}] must not include phrases starting with "not "; refactor to be a positive assertion: ${inspect(
49
57
  part,
50
58
  )}`,
51
59
  );
52
60
  }
53
- result.push(
54
- z
55
- .literal(part)
56
- .brand('string-literal')
57
- .register(BupkisRegistry, {
58
- [kStringLiteral]: true,
59
- values: part,
60
- }),
61
- );
61
+ result.push(createPhraseLiteralChoiceSchema(part));
62
62
  } else if (isPhraseLiteral(part)) {
63
63
  if (part.startsWith('not ')) {
64
64
  throw new AssertionImplementationError(
@@ -67,15 +67,19 @@ export const slotify = <const Parts extends AssertionParts>(
67
67
  )}`,
68
68
  );
69
69
  }
70
- result.push(
71
- z
72
- .literal(part)
73
- .brand('string-literal')
74
- .register(BupkisRegistry, {
75
- [kStringLiteral]: true,
76
- value: part,
77
- }),
78
- );
70
+ result.push(createPhraseLiteralSchema(part));
71
+ } else if (typeof part === 'string' && part === 'and') {
72
+ // Special case: "and" is allowed when followed by a ZodType (for conjunctify)
73
+ if (index + 1 >= parts.length || !isZodType(parts[index + 1])) {
74
+ throw new AssertionImplementationError(
75
+ `"and" at parts[${index}] must be followed by a Zod schema but was followed by ${
76
+ index + 1 >= parts.length
77
+ ? 'nothing'
78
+ : `${inspect(parts[index + 1])} (${typeof parts[index + 1]})`
79
+ }`,
80
+ );
81
+ }
82
+ result.push(createPhraseLiteralSchema(part));
79
83
  } else {
80
84
  if (!isZodType(part)) {
81
85
  throw new AssertionImplementationError(
@@ -88,3 +92,45 @@ export const slotify = <const Parts extends AssertionParts>(
88
92
  }
89
93
  return result;
90
94
  }) as unknown as AssertionSlots<Parts>;
95
+
96
+ /**
97
+ * Creates a schema for a choice of phrase literals
98
+ *
99
+ * This schema is a branded literal schema to differentiate regular strings from
100
+ * phrases.
101
+ *
102
+ * @function
103
+ * @param part Phrase literal choice (tuple of strings)
104
+ * @returns Schema
105
+ */
106
+ const createPhraseLiteralChoiceSchema = (
107
+ part: PhraseLiteralChoice,
108
+ ): z.core.$ZodBranded<z.ZodLiteral<string>, 'string-literal'> =>
109
+ z
110
+ .literal(part)
111
+ .brand('string-literal')
112
+ .register(BupkisRegistry, {
113
+ [kStringLiteral]: true,
114
+ values: part,
115
+ });
116
+
117
+ /**
118
+ * Creates a schema for a single phrase literal
119
+ *
120
+ * This schema is a branded literal schema to differentiate regular strings from
121
+ * phrases.
122
+ *
123
+ * @function
124
+ * @param part Phrase literal (string)
125
+ * @returns Schema
126
+ */
127
+ const createPhraseLiteralSchema = (
128
+ part: PhraseLiteral,
129
+ ): z.core.$ZodBranded<z.ZodLiteral<string>, 'string-literal'> =>
130
+ z
131
+ .literal(part)
132
+ .brand('string-literal')
133
+ .register(BupkisRegistry, {
134
+ [kStringLiteral]: true,
135
+ value: part,
136
+ });
package/src/bootstrap.ts CHANGED
@@ -15,6 +15,7 @@ import { createUse } from './use.js';
15
15
  * Factory function that creates both synchronous and asynchronous assertion
16
16
  * engines.
17
17
  *
18
+ * @function
18
19
  * @returns Object containing {@link expect} and {@link expectAsync} functions
19
20
  * @internal
20
21
  */