bupkis 0.1.2 → 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 (198) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +16 -16
  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 +39 -84
  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/create.d.ts +5 -33
  18. package/dist/commonjs/assertion/create.d.ts.map +1 -1
  19. package/dist/commonjs/assertion/create.js +17 -6
  20. package/dist/commonjs/assertion/create.js.map +1 -1
  21. package/dist/commonjs/assertion/impl/async.d.ts +122 -21
  22. package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
  23. package/dist/commonjs/assertion/impl/async.js +114 -90
  24. package/dist/commonjs/assertion/impl/async.js.map +1 -1
  25. package/dist/commonjs/assertion/impl/callback.d.ts +104 -0
  26. package/dist/commonjs/assertion/impl/callback.d.ts.map +1 -0
  27. package/dist/commonjs/assertion/impl/callback.js +694 -0
  28. package/dist/commonjs/assertion/impl/callback.js.map +1 -0
  29. package/dist/commonjs/assertion/impl/index.d.ts +1 -1
  30. package/dist/commonjs/assertion/impl/index.d.ts.map +1 -1
  31. package/dist/commonjs/assertion/impl/index.js.map +1 -1
  32. package/dist/commonjs/assertion/impl/sync-esoteric.js +1 -1
  33. package/dist/commonjs/assertion/impl/sync-esoteric.js.map +1 -1
  34. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +37 -34
  35. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  36. package/dist/commonjs/assertion/impl/sync-parametric.js +32 -47
  37. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  38. package/dist/commonjs/assertion/impl/sync.d.ts +105 -58
  39. package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
  40. package/dist/commonjs/assertion/impl/sync.js +4 -1
  41. package/dist/commonjs/assertion/impl/sync.js.map +1 -1
  42. package/dist/commonjs/bootstrap.d.ts +199 -85
  43. package/dist/commonjs/bootstrap.d.ts.map +1 -1
  44. package/dist/commonjs/bootstrap.js +19 -10
  45. package/dist/commonjs/bootstrap.js.map +1 -1
  46. package/dist/commonjs/constant.js +7 -1
  47. package/dist/commonjs/constant.js.map +1 -1
  48. package/dist/commonjs/error.d.ts +32 -5
  49. package/dist/commonjs/error.d.ts.map +1 -1
  50. package/dist/commonjs/error.js +60 -5
  51. package/dist/commonjs/error.js.map +1 -1
  52. package/dist/commonjs/expect.d.ts +130 -3
  53. package/dist/commonjs/expect.d.ts.map +1 -1
  54. package/dist/commonjs/expect.js +116 -1
  55. package/dist/commonjs/expect.js.map +1 -1
  56. package/dist/commonjs/guards.d.ts +45 -20
  57. package/dist/commonjs/guards.d.ts.map +1 -1
  58. package/dist/commonjs/guards.js +56 -40
  59. package/dist/commonjs/guards.js.map +1 -1
  60. package/dist/commonjs/index.d.ts +241 -86
  61. package/dist/commonjs/index.d.ts.map +1 -1
  62. package/dist/commonjs/index.js +44 -42
  63. package/dist/commonjs/index.js.map +1 -1
  64. package/dist/commonjs/metadata.d.ts +1 -27
  65. package/dist/commonjs/metadata.d.ts.map +1 -1
  66. package/dist/commonjs/metadata.js +16 -15
  67. package/dist/commonjs/metadata.js.map +1 -1
  68. package/dist/commonjs/schema.d.ts +76 -33
  69. package/dist/commonjs/schema.d.ts.map +1 -1
  70. package/dist/commonjs/schema.js +77 -34
  71. package/dist/commonjs/schema.js.map +1 -1
  72. package/dist/commonjs/types.d.ts +480 -39
  73. package/dist/commonjs/types.d.ts.map +1 -1
  74. package/dist/commonjs/types.js +12 -2
  75. package/dist/commonjs/types.js.map +1 -1
  76. package/dist/commonjs/util.d.ts +72 -49
  77. package/dist/commonjs/util.d.ts.map +1 -1
  78. package/dist/commonjs/util.js +175 -155
  79. package/dist/commonjs/util.js.map +1 -1
  80. package/dist/commonjs/value-to-schema.d.ts +122 -0
  81. package/dist/commonjs/value-to-schema.d.ts.map +1 -0
  82. package/dist/commonjs/value-to-schema.js +309 -0
  83. package/dist/commonjs/value-to-schema.js.map +1 -0
  84. package/dist/esm/assertion/assertion-async.d.ts +2 -1
  85. package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
  86. package/dist/esm/assertion/assertion-async.js +85 -3
  87. package/dist/esm/assertion/assertion-async.js.map +1 -1
  88. package/dist/esm/assertion/assertion-sync.d.ts +1 -1
  89. package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
  90. package/dist/esm/assertion/assertion-sync.js +6 -2
  91. package/dist/esm/assertion/assertion-sync.js.map +1 -1
  92. package/dist/esm/assertion/assertion-types.d.ts +39 -84
  93. package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
  94. package/dist/esm/assertion/assertion.d.ts +1 -1
  95. package/dist/esm/assertion/assertion.d.ts.map +1 -1
  96. package/dist/esm/assertion/assertion.js +1 -14
  97. package/dist/esm/assertion/assertion.js.map +1 -1
  98. package/dist/esm/assertion/create.d.ts +5 -33
  99. package/dist/esm/assertion/create.d.ts.map +1 -1
  100. package/dist/esm/assertion/create.js +14 -4
  101. package/dist/esm/assertion/create.js.map +1 -1
  102. package/dist/esm/assertion/impl/async.d.ts +122 -21
  103. package/dist/esm/assertion/impl/async.d.ts.map +1 -1
  104. package/dist/esm/assertion/impl/async.js +113 -89
  105. package/dist/esm/assertion/impl/async.js.map +1 -1
  106. package/dist/esm/assertion/impl/callback.d.ts +104 -0
  107. package/dist/esm/assertion/impl/callback.d.ts.map +1 -0
  108. package/dist/esm/assertion/impl/callback.js +691 -0
  109. package/dist/esm/assertion/impl/callback.js.map +1 -0
  110. package/dist/esm/assertion/impl/index.d.ts +1 -1
  111. package/dist/esm/assertion/impl/index.d.ts.map +1 -1
  112. package/dist/esm/assertion/impl/index.js +1 -1
  113. package/dist/esm/assertion/impl/index.js.map +1 -1
  114. package/dist/esm/assertion/impl/sync-esoteric.js +2 -2
  115. package/dist/esm/assertion/impl/sync-esoteric.js.map +1 -1
  116. package/dist/esm/assertion/impl/sync-parametric.d.ts +37 -34
  117. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  118. package/dist/esm/assertion/impl/sync-parametric.js +32 -47
  119. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  120. package/dist/esm/assertion/impl/sync.d.ts +105 -58
  121. package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
  122. package/dist/esm/assertion/impl/sync.js +3 -1
  123. package/dist/esm/assertion/impl/sync.js.map +1 -1
  124. package/dist/esm/bootstrap.d.ts +199 -85
  125. package/dist/esm/bootstrap.d.ts.map +1 -1
  126. package/dist/esm/bootstrap.js +19 -10
  127. package/dist/esm/bootstrap.js.map +1 -1
  128. package/dist/esm/constant.js +6 -0
  129. package/dist/esm/constant.js.map +1 -1
  130. package/dist/esm/error.d.ts +32 -5
  131. package/dist/esm/error.d.ts.map +1 -1
  132. package/dist/esm/error.js +59 -5
  133. package/dist/esm/error.js.map +1 -1
  134. package/dist/esm/expect.d.ts +130 -3
  135. package/dist/esm/expect.d.ts.map +1 -1
  136. package/dist/esm/expect.js +117 -2
  137. package/dist/esm/expect.js.map +1 -1
  138. package/dist/esm/guards.d.ts +45 -20
  139. package/dist/esm/guards.d.ts.map +1 -1
  140. package/dist/esm/guards.js +48 -31
  141. package/dist/esm/guards.js.map +1 -1
  142. package/dist/esm/index.d.ts +241 -86
  143. package/dist/esm/index.d.ts.map +1 -1
  144. package/dist/esm/index.js +46 -7
  145. package/dist/esm/index.js.map +1 -1
  146. package/dist/esm/metadata.d.ts +1 -27
  147. package/dist/esm/metadata.d.ts.map +1 -1
  148. package/dist/esm/metadata.js +2 -1
  149. package/dist/esm/metadata.js.map +1 -1
  150. package/dist/esm/schema.d.ts +76 -33
  151. package/dist/esm/schema.d.ts.map +1 -1
  152. package/dist/esm/schema.js +77 -34
  153. package/dist/esm/schema.js.map +1 -1
  154. package/dist/esm/types.d.ts +480 -39
  155. package/dist/esm/types.d.ts.map +1 -1
  156. package/dist/esm/types.js +12 -2
  157. package/dist/esm/types.js.map +1 -1
  158. package/dist/esm/util.d.ts +72 -49
  159. package/dist/esm/util.d.ts.map +1 -1
  160. package/dist/esm/util.js +159 -153
  161. package/dist/esm/util.js.map +1 -1
  162. package/dist/esm/value-to-schema.d.ts +122 -0
  163. package/dist/esm/value-to-schema.d.ts.map +1 -0
  164. package/dist/esm/value-to-schema.js +305 -0
  165. package/dist/esm/value-to-schema.js.map +1 -0
  166. package/package.json +94 -17
  167. package/src/assertion/assertion-async.ts +113 -3
  168. package/src/assertion/assertion-sync.ts +5 -2
  169. package/src/assertion/assertion-types.ts +52 -45
  170. package/src/assertion/assertion.ts +2 -17
  171. package/src/assertion/create.ts +16 -65
  172. package/src/assertion/impl/async.ts +132 -92
  173. package/src/assertion/impl/callback.ts +882 -0
  174. package/src/assertion/impl/index.ts +1 -1
  175. package/src/assertion/impl/sync-esoteric.ts +2 -2
  176. package/src/assertion/impl/sync-parametric.ts +41 -49
  177. package/src/assertion/impl/sync.ts +3 -0
  178. package/src/bootstrap.ts +21 -11
  179. package/src/constant.ts +8 -0
  180. package/src/error.ts +75 -4
  181. package/src/expect.ts +275 -20
  182. package/src/guards.ts +74 -69
  183. package/src/index.ts +72 -11
  184. package/src/metadata.ts +3 -4
  185. package/src/schema.ts +80 -36
  186. package/src/types.ts +625 -72
  187. package/src/util.ts +174 -222
  188. package/src/value-to-schema.ts +464 -0
  189. package/dist/commonjs/api.d.ts +0 -93
  190. package/dist/commonjs/api.d.ts.map +0 -1
  191. package/dist/commonjs/api.js +0 -8
  192. package/dist/commonjs/api.js.map +0 -1
  193. package/dist/esm/api.d.ts +0 -93
  194. package/dist/esm/api.d.ts.map +0 -1
  195. package/dist/esm/api.js +0 -7
  196. package/dist/esm/api.js.map +0 -1
  197. package/src/api.ts +0 -149
  198. package/src/schema.md +0 -15
package/src/expect.ts CHANGED
@@ -1,14 +1,6 @@
1
1
  import Debug from 'debug';
2
2
  import { inspect } from 'util';
3
3
 
4
- import {
5
- type Expect,
6
- type ExpectAsync,
7
- type ExpectAsyncFunction,
8
- type ExpectAsyncProps,
9
- type ExpectFunction,
10
- type ExpectSyncProps,
11
- } from './api.js';
12
4
  import {
13
5
  type AnyAsyncAssertion,
14
6
  type AnyAsyncAssertions,
@@ -24,19 +16,160 @@ import {
24
16
  type ParsedValues,
25
17
  } from './assertion/assertion-types.js';
26
18
  import { createAssertion, createAsyncAssertion } from './assertion/create.js';
27
- import { AssertionError, NegatedAssertionError } from './error.js';
19
+ import {
20
+ AssertionError,
21
+ FailAssertionError,
22
+ NegatedAssertionError,
23
+ } from './error.js';
28
24
  import { isAssertionFailure, isString } from './guards.js';
25
+ import {
26
+ type Expect,
27
+ type ExpectAsync,
28
+ type ExpectAsyncFunction,
29
+ type ExpectAsyncProps,
30
+ type ExpectFunction,
31
+ type ExpectSyncProps,
32
+ type FailFn,
33
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
34
+ type UseFn,
35
+ } from './types.js';
29
36
  import { createUse } from './use.js';
30
37
 
31
38
  const debug = Debug('bupkis:expect');
32
39
 
40
+ /**
41
+ * Creates an asynchronous expect function by extending a parent expectAsync
42
+ * function with additional assertions.
43
+ *
44
+ * This overload combines assertions from an existing parent expectAsync
45
+ * function with new assertions, creating a unified expectAsync function that
46
+ * supports both sets of assertions. The resulting function inherits all type
47
+ * information from both the parent and new assertions, providing complete
48
+ * TypeScript intellisense and type safety for Promise-based testing scenarios.
49
+ *
50
+ * @example
51
+ *
52
+ * ```typescript
53
+ * const baseExpectAsync = createExpectAsyncFunction(basicAsyncAssertions);
54
+ * const extendedExpectAsync = createExpectAsyncFunction(
55
+ * customAsyncAssertions,
56
+ * baseExpectAsync,
57
+ * );
58
+ *
59
+ * // Can use both basic and custom async assertions
60
+ * await extendedExpectAsync(promise, 'to resolve'); // From basic assertions
61
+ * await extendedExpectAsync(promise, 'to resolve with custom data'); // From custom assertions
62
+ * ```
63
+ *
64
+ * @param assertions - Array of new asynchronous assertion objects to add
65
+ * @param expect - Parent expectAsync function whose assertions will be
66
+ * inherited
67
+ * @returns ExpectAsync function with combined assertion types from both parent
68
+ * and new assertions
69
+ * @throws {@link AssertionError} When an assertion fails in normal
70
+ * (non-negated) mode
71
+ * @throws {@link NegatedAssertionError} When a negated assertion fails
72
+ * @throws {Error} When no matching assertion can be found for the provided
73
+ * arguments
74
+ */
33
75
  export function createExpectAsyncFunction<
34
76
  T extends AnyAsyncAssertions,
35
77
  U extends ExpectAsync<AnyAsyncAssertions>,
36
78
  >(assertions: T, expect: U): ExpectAsyncFunction<T & U['assertions']>;
79
+
80
+ /**
81
+ * Creates a new asynchronous expect function with the provided assertions.
82
+ *
83
+ * This overload creates a standalone expectAsync function from the provided
84
+ * assertions without inheriting from any parent function. This is typically
85
+ * used to create the initial expectAsync function or when you want a clean
86
+ * slate without any inherited assertions for Promise-based testing.
87
+ *
88
+ * @example
89
+ *
90
+ * ```typescript
91
+ * const expectAsync = createExpectAsyncFunction(asyncAssertions);
92
+ * await expectAsync(promise, 'to resolve');
93
+ * await expectAsync(rejectedPromise, 'to reject');
94
+ * await expectAsync(promise, 'to resolve to', expectedValue);
95
+ * ```
96
+ *
97
+ * @param assertions - Array of asynchronous assertion objects that define the
98
+ * available assertion phrases and Promise-based logic
99
+ * @returns An asynchronous expect function that can execute the provided
100
+ * assertions using natural language syntax
101
+ * @throws {@link AssertionError} When an assertion fails in normal
102
+ * (non-negated) mode
103
+ * @throws {@link NegatedAssertionError} When a negated assertion fails
104
+ * @throws {Error} When no matching assertion can be found for the provided
105
+ * arguments
106
+ */
37
107
  export function createExpectAsyncFunction<T extends AnyAsyncAssertions>(
38
108
  assertions: T,
39
109
  ): ExpectAsyncFunction<T>;
110
+
111
+ /**
112
+ * Implementation function that creates an asynchronous expect function with
113
+ * optional parent inheritance.
114
+ *
115
+ * This is the concrete implementation that handles both overload cases for
116
+ * Promise-based assertions. It creates an expectAsync function that uses a
117
+ * two-phase matching algorithm: first seeking exact phrase matches for optimal
118
+ * performance, then falling back to partial matches if needed. The function
119
+ * processes negation keywords, combines parent assertions with new ones, and
120
+ * ensures all operations are properly awaited.
121
+ *
122
+ * The matching algorithm prioritizes exact matches to minimize performance
123
+ * overhead, but provides flexibility through partial matching when exact
124
+ * phrases don't align. This enables natural language flexibility while
125
+ * maintaining execution speed for common async assertion patterns.
126
+ *
127
+ * @remarks
128
+ * The function performs async assertion matching in the following order:
129
+ *
130
+ * 1. Awaits `Promise.resolve()` to ensure the function is always asynchronous
131
+ * 2. Processes negation keywords ('not', 'to not') to determine assertion mode
132
+ * 3. Combines parent assertions (if provided) with new assertions in execution
133
+ * order
134
+ * 4. Attempts to parse arguments against each assertion's expected phrase pattern
135
+ * using `parseValuesAsync`
136
+ * 5. Prioritizes exact phrase matches over partial matches for performance
137
+ * 6. Executes the first successful match using {@link executeAsync} or throws an
138
+ * error if none found
139
+ *
140
+ * Performance considerations: The function loops through all available
141
+ * assertions for each call, but uses early termination when exact matches are
142
+ * found. For performance-critical code, consider using assertion functions with
143
+ * fewer total assertions or more specific phrase patterns to reduce matching
144
+ * overhead.
145
+ *
146
+ * All assertion execution is properly awaited to handle Promise-based
147
+ * validation logic, error handling, and negation scenarios in asynchronous
148
+ * contexts.
149
+ * @example
150
+ *
151
+ * ```typescript
152
+ * // Used internally by both public overloads
153
+ * const expectAsync1 = createExpectAsyncFunction(assertions); // No parent
154
+ * const expectAsync2 = createExpectAsyncFunction(assertions, parent); // With parent
155
+ * ```
156
+ *
157
+ * @param assertions - Array of asynchronous assertion objects to make available
158
+ * @param expect - Optional parent expectAsync function to inherit assertions
159
+ * from
160
+ * @returns Asynchronous expect function that processes natural language
161
+ * assertions with Promise support
162
+ * @throws {@link AssertionError} When an assertion fails in normal
163
+ * (non-negated) mode
164
+ * @throws {@link NegatedAssertionError} When a negated assertion fails (e.g.,
165
+ * `await expectAsync(promise, 'not to resolve')`)
166
+ * @throws {Error} When no matching assertion can be found for the provided
167
+ * arguments
168
+ * @internal This is the concrete implementation used by the public overloads
169
+ * @see {@link createExpectSyncFunction} for creating synchronous expect functions
170
+ * @see {@link createAsyncAssertion} for creating individual async assertion objects
171
+ * @see {@link ExpectAsync} for the main expectAsync interface
172
+ */
40
173
  export function createExpectAsyncFunction<
41
174
  T extends AnyAsyncAssertions,
42
175
  U extends ExpectAsync<AnyAsyncAssertions>,
@@ -87,18 +220,136 @@ export function createExpectAsyncFunction<
87
220
  return expectAsyncFunction;
88
221
  }
89
222
 
223
+ /**
224
+ * Creates a synchronous expect function by extending a parent expect function
225
+ * with additional assertions.
226
+ *
227
+ * This overload combines assertions from an existing parent expect function
228
+ * with new assertions, creating a unified expect function that supports both
229
+ * sets of assertions. The resulting function inherits all type information from
230
+ * both the parent and new assertions, providing complete TypeScript
231
+ * intellisense and type safety.
232
+ *
233
+ * @example
234
+ *
235
+ * ```typescript
236
+ * const baseExpect = createExpectSyncFunction(basicAssertions);
237
+ * const extendedExpect = createExpectSyncFunction(
238
+ * customAssertions,
239
+ * baseExpect,
240
+ * );
241
+ *
242
+ * // Can use both basic and custom assertions
243
+ * extendedExpect(42, 'to be a number'); // From basic assertions
244
+ * extendedExpect(obj, 'to have custom prop'); // From custom assertions
245
+ * ```
246
+ *
247
+ * @param assertions - Array of new synchronous assertion objects to add
248
+ * @param expect - Parent expect function whose assertions will be inherited
249
+ * @returns Expect function with combined assertion types from both parent and
250
+ * new assertions
251
+ * @throws {@link AssertionError} When an assertion fails in normal
252
+ * (non-negated) mode
253
+ * @throws {@link NegatedAssertionError} When a negated assertion fails
254
+ * @throws {Error} When no matching assertion can be found for the provided
255
+ * arguments
256
+ */
90
257
  export function createExpectSyncFunction<
91
- T extends AnySyncAssertions,
92
- U extends Expect<AnySyncAssertions>,
93
- >(assertions: T, expect: U): ExpectFunction<T & U['assertions']>;
258
+ Assertions extends AnySyncAssertions,
259
+ ParentExpect extends Expect<AnySyncAssertions>,
260
+ >(
261
+ assertions: Assertions,
262
+ expect: ParentExpect,
263
+ ): ExpectFunction<Assertions & ParentExpect['assertions']>;
94
264
 
95
- export function createExpectSyncFunction<T extends AnySyncAssertions>(
96
- assertions: T,
97
- ): ExpectFunction<T>;
265
+ /**
266
+ * Creates a new synchronous expect function with the provided assertions.
267
+ *
268
+ * This overload creates a standalone expect function from the provided
269
+ * assertions without inheriting from any parent function. This is typically
270
+ * used to create the initial expect function or when you want a clean slate
271
+ * without any inherited assertions.
272
+ *
273
+ * @example
274
+ *
275
+ * ```typescript
276
+ * const expect = createExpectSyncFunction(basicAssertions);
277
+ * expect(42, 'to be a number');
278
+ * expect('hello', 'to be a string');
279
+ * expect([], 'to be empty');
280
+ * ```
281
+ *
282
+ * @param assertions - Array of synchronous assertion objects that define the
283
+ * available assertion phrases and logic
284
+ * @returns A synchronous expect function that can execute the provided
285
+ * assertions using natural language syntax
286
+ * @throws {@link AssertionError} When an assertion fails in normal
287
+ * (non-negated) mode
288
+ * @throws {@link NegatedAssertionError} When a negated assertion fails
289
+ * @throws {Error} When no matching assertion can be found for the provided
290
+ * arguments
291
+ */
292
+ export function createExpectSyncFunction<Assertions extends AnySyncAssertions>(
293
+ assertions: Assertions,
294
+ ): ExpectFunction<Assertions>;
295
+
296
+ /**
297
+ * Implementation function that creates a synchronous expect function with
298
+ * optional parent inheritance.
299
+ *
300
+ * This is the concrete implementation that handles both overload cases. It
301
+ * creates an expect function that uses a two-phase matching algorithm: first
302
+ * seeking exact phrase matches for optimal performance, then falling back to
303
+ * partial matches if needed. The function processes negation keywords and
304
+ * combines parent assertions with new ones.
305
+ *
306
+ * The matching algorithm prioritizes exact matches to minimize performance
307
+ * overhead, but provides flexibility through partial matching when exact
308
+ * phrases don't align. This enables natural language flexibility while
309
+ * maintaining execution speed for common assertion patterns.
310
+ *
311
+ * @remarks
312
+ * The function performs assertion matching in the following order:
313
+ *
314
+ * 1. Processes negation keywords ('not', 'to not') to determine assertion mode
315
+ * 2. Combines parent assertions (if provided) with new assertions in execution
316
+ * order
317
+ * 3. Attempts to parse arguments against each assertion's expected phrase pattern
318
+ * 4. Prioritizes exact phrase matches over partial matches for performance
319
+ * 5. Executes the first successful match or throws an error if none found
320
+ *
321
+ * Performance considerations: The function loops through all available
322
+ * assertions for each call, but uses early termination when exact matches are
323
+ * found. For performance-critical code, consider using assertion functions with
324
+ * fewer total assertions or more specific phrase patterns to reduce matching
325
+ * overhead.
326
+ * @example
327
+ *
328
+ * ```typescript
329
+ * // Used internally by both public overloads
330
+ * const expect1 = createExpectSyncFunction(assertions); // No parent
331
+ * const expect2 = createExpectSyncFunction(assertions, parent); // With parent
332
+ * ```
333
+ *
334
+ * @param assertions - Array of synchronous assertion objects to make available
335
+ * @param expect - Optional parent expect function to inherit assertions from
336
+ * @returns Synchronous expect function that processes natural language
337
+ * assertions
338
+ * @throws {@link AssertionError} When an assertion fails in normal
339
+ * (non-negated) mode
340
+ * @throws {@link NegatedAssertionError} When a negated assertion fails (e.g.,
341
+ * `expect(42, 'not to be a number')`)
342
+ * @throws {Error} When no matching assertion can be found for the provided
343
+ * arguments
344
+ * @internal This is the concrete implementation used by the public overloads
345
+ * @see {@link createExpectAsyncFunction} for creating asynchronous expect functions
346
+ * @see {@link createAssertion} for creating individual assertion objects
347
+ * @see {@link Expect} for the main expect interface
348
+ */
98
349
  export function createExpectSyncFunction<
99
- T extends AnySyncAssertions,
100
- U extends Expect<AnySyncAssertions>,
101
- >(assertions: T, expect?: U) {
350
+ Assertions extends AnySyncAssertions,
351
+ ParentExpect extends Expect<AnySyncAssertions>,
352
+ >(assertions: Assertions, expect?: ParentExpect) {
102
353
  debug(
103
354
  'Creating expect function with %d assertions',
104
355
  assertions.length + (expect?.assertions.length ?? 0),
@@ -330,10 +581,14 @@ const detectNegation = (
330
581
  };
331
582
  };
332
583
 
333
- const fail = (reason?: string): never => {
334
- throw new AssertionError({ message: reason });
584
+ const fail: FailFn = (reason?: string): never => {
585
+ throw new FailAssertionError({ message: reason });
335
586
  };
336
587
 
588
+ /**
589
+ * Used by a {@link UseFn} to create base properties of the {@link Expect} and
590
+ * {@link ExpectAsync} functions.
591
+ */
337
592
  export function createBaseExpect<
338
593
  T extends AnySyncAssertions,
339
594
  U extends AnyAsyncAssertions,
package/src/guards.ts CHANGED
@@ -2,52 +2,91 @@
2
2
  * Type guard functions and runtime type checking utilities.
3
3
  *
4
4
  * This module provides various type guard functions for runtime type checking,
5
- * including guards for Zod schemas, constructors, Promise-like objects, and
6
- * assertion parts. These are used throughout the library for safe type
5
+ * including guards for Zod schemas, constructors, {@link PromiseLike} objects,
6
+ * and assertion parts. These are used throughout the library for safe type
7
7
  * narrowing and validation.
8
8
  *
9
+ * @category API
10
+ * @example
11
+ *
12
+ * ```ts
13
+ * import * as guards from 'bupkis/guards';
14
+ * ```
15
+ *
9
16
  * @packageDocumentation
10
17
  */
11
18
 
12
19
  import { type Primitive } from 'type-fest';
13
- import { z } from 'zod';
20
+ import { z } from 'zod/v4';
14
21
 
15
22
  import type {
16
23
  AssertionFailure,
17
24
  AssertionPart,
18
25
  PhraseLiteralChoice,
19
26
  } from './assertion/assertion-types.js';
20
- import type { Constructor } from './types.js';
27
+ import type { Constructor, ZodTypeMap } from './types.js';
21
28
 
22
29
  /**
23
- * Returns true if the given value looks like a Zod schema (v4), determined by
24
- * the presence of an internal `def.type` field.
30
+ * Returns `true` if the given value looks like a Zod v4 schema, determined by
31
+ * the presence of an internal {@link z.core.$ZodTypeDef} field.
25
32
  *
26
33
  * Note: This relies on Zod's internal shape and is intended for runtime
27
34
  * discrimination within this library.
28
35
  *
29
- * @template T
36
+ * @template T - The specific ZodType to check for (based on def.type)
30
37
  * @param value - Value to test
31
- * @returns Whether the value is Zod-like
38
+ * @returns Whether the value is `ZodType`-like
32
39
  */
33
- export const isZodType = (value: unknown): value is z.ZodType =>
34
- !!(
35
- value &&
36
- 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) &&
37
61
  'def' in value &&
38
- value.def &&
62
+ !!value.def &&
39
63
  typeof value.def === 'object' &&
40
- 'type' in value.def
41
- );
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
+ }
42
71
 
43
72
  /**
44
- * Returns true if the given value is a {@link z.ZodPromise} schema.
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
+ };
81
+
82
+ /**
83
+ * Returns `true` if the given value is a {@link z.ZodPromise} schema.
45
84
  *
46
85
  * @param value - Value to test
47
86
  * @returns `true` if the value is a `ZodPromise` schema; `false` otherwise
48
87
  */
49
88
  export const isZodPromise = (value: unknown): value is z.ZodPromise =>
50
- isZodType(value) && value.def.type === 'promise';
89
+ isZodType(value, 'promise');
51
90
 
52
91
  /**
53
92
  * Checks if a value is "promise-like", meaning it is a "thenable" object.
@@ -56,23 +95,24 @@ export const isZodPromise = (value: unknown): value is z.ZodPromise =>
56
95
  * @returns `true` if the value is promise-like, `false` otherwise
57
96
  */
58
97
  export const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>
59
- !!(
60
- value &&
61
- typeof value === 'object' &&
62
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
63
- typeof (value as any).then === 'function'
64
- );
98
+ isObject(value) && 'then' in value && isFunction(value.then);
65
99
 
66
100
  /**
67
- * Returns true if the given value is a constructable function (i.e., a class).
101
+ * Returns `true` if the given value is a constructable function (i.e., a
102
+ * class).
68
103
  *
69
- * This may be the only way we can determine, at runtime, if a function is a
70
- * constructor without actually calling it.
104
+ * This works by wrapping `fn` in a {@link Proxy}, attaching a no-op
105
+ * {@link ProxyHandler.construct} trap to it, then attempting to construct the
106
+ * proxy via `new`.
71
107
  *
108
+ * @privateRemarks
109
+ * This may be the only way we can determine, at runtime, if a function is a
110
+ * constructor without actually calling it. I am unsure if this only works for
111
+ * classes.
72
112
  * @param fn - Function to test
73
113
  * @returns Whether the function is constructable
74
114
  */
75
- export const isConstructable = (fn: unknown): fn is Constructor => {
115
+ export const isConstructible = (fn: unknown): fn is Constructor => {
76
116
  if (fn === Symbol || fn === BigInt) {
77
117
  return false;
78
118
  }
@@ -121,14 +161,16 @@ const AssertionFailureSchema: z.ZodType<AssertionFailure> = z.object({
121
161
  .describe('A human-readable message describing the failure'),
122
162
  });
123
163
 
164
+ /**
165
+ * Type guard for a {@link AssertionFailure} object
166
+ *
167
+ * @param value Value to check
168
+ * @returns `true` if the value is an `AssertionFailure`, `false` otherwise
169
+ * @internal
170
+ */
124
171
  export const isAssertionFailure = (value: unknown): value is AssertionFailure =>
125
172
  AssertionFailureSchema.safeParse(value).success;
126
173
 
127
- export const isAsyncFunction = (
128
- value: unknown,
129
- ): value is (...args: any[]) => Promise<any> =>
130
- isFunction(value) && value.constructor.name === 'AsyncFunction';
131
-
132
174
  /**
133
175
  * Type guard for a string value
134
176
  *
@@ -179,43 +221,6 @@ export const isPhraseLiteralChoice = (
179
221
  export const isPhraseLiteral = (value: AssertionPart): value is string =>
180
222
  isString(value) && !value.startsWith('not ');
181
223
 
182
- export type PrimitiveTypeName =
183
- | 'bigint'
184
- | 'boolean'
185
- | 'function'
186
- | 'null'
187
- | 'number'
188
- | 'object'
189
- | 'string'
190
- | 'symbol'
191
- | 'undefined';
192
-
193
- export type PrimitiveTypeNameToType<T extends PrimitiveTypeName> =
194
- T extends 'undefined'
195
- ? undefined
196
- : T extends 'object'
197
- ? null | object
198
- : T extends 'function'
199
- ? (...args: any[]) => any
200
- : T extends 'string'
201
- ? string
202
- : T extends 'number'
203
- ? number
204
- : T extends 'boolean'
205
- ? boolean
206
- : T extends 'bigint'
207
- ? bigint
208
- : T extends 'symbol'
209
- ? symbol
210
- : never;
211
-
212
- export const isType = <T extends PrimitiveTypeName>(
213
- a: unknown,
214
- b: T,
215
- ): a is PrimitiveTypeNameToType<T> => {
216
- return typeof a === b;
217
- };
218
-
219
224
  export const isA = <T extends Constructor>(
220
225
  value: unknown,
221
226
  ctor: T,
package/src/index.ts CHANGED
@@ -6,24 +6,85 @@
6
6
  * guards, schema definitions, utility functions, and error types.
7
7
  *
8
8
  * @module bupkis
9
+ * @category API
10
+ * @example
11
+ *
12
+ * ```ts
13
+ * import { expect, expectAsync, z, createAssertion } from 'bupkis';
14
+ * ```
15
+ *
16
+ * @showGroups
9
17
  */
10
18
 
11
- import { expect as sacrificialExpect } from './bootstrap.js';
12
- export type * from './api.js';
19
+ import { z } from 'zod/v4';
13
20
 
14
- export * as assertion from './assertion/index.js';
21
+ import { expect as sacrificialExpect } from './bootstrap.js';
15
22
  export { expect, expectAsync } from './bootstrap.js';
16
23
 
17
- export * as error from './error.js';
18
- export * as guards from './guards.js';
24
+ export { AssertionError } from './error.js';
19
25
 
20
- export type * as metadata from './metadata.js';
21
- export * as schema from './schema.js';
26
+ /**
27
+ * Re-export of most (all?) types defined within <span
28
+ * class="bupkis">Bupkis</span>.
29
+ *
30
+ * @example
31
+ *
32
+ * ```ts
33
+ * import { types } from 'bupkis';
34
+ * ```
35
+ */
22
36
  export type * as types from './types.js';
23
- export * as util from './util.js';
24
37
 
25
- export { z } from 'zod/v4';
38
+ /**
39
+ * Re-export of {@link https://zod.dev Zod v4} for use in custom assertion
40
+ * implementations.
41
+ */
42
+ export { z };
26
43
 
44
+ /**
45
+ * @primaryExport
46
+ */
47
+ export type {
48
+ Bupkis,
49
+ CreateAssertionFn,
50
+ CreateAsyncAssertionFn,
51
+ Expect,
52
+ ExpectAsync,
53
+ FailFn,
54
+ UseFn,
55
+ ZodTypeMap,
56
+ } from './types.js';
27
57
  export { createAssertion, createAsyncAssertion, fail, use };
28
- const { createAssertion, createAsyncAssertion, fail, use, ..._rest } =
29
- sacrificialExpect;
58
+ const {
59
+ /**
60
+ * The main factory function for creating asynchronous assertions.
61
+ *
62
+ * Exported from the entry point; is also a property of {@link Expect} and
63
+ * {@link ExpectAsync}.
64
+ *
65
+ * @function
66
+ */
67
+ createAssertion,
68
+ /**
69
+ * The main factory function for creating asynchronous assertions.
70
+ *
71
+ * Exported from the entry point; is also a property of {@link Expect} and
72
+ * {@link ExpectAsync}.
73
+ *
74
+ * @function
75
+ */
76
+ createAsyncAssertion,
77
+ /**
78
+ * {@inheritDoc FailFn}
79
+ *
80
+ * @function
81
+ */
82
+ fail,
83
+ /**
84
+ * {@inheritDoc UseFn}
85
+ *
86
+ * @function
87
+ */
88
+ use,
89
+ ..._rest
90
+ } = sacrificialExpect;
package/src/metadata.ts CHANGED
@@ -2,18 +2,17 @@
2
2
  * Defines Bupkis' Zod metadata registry
3
3
  *
4
4
  * @packageDocumentation
5
+ * @internal
5
6
  */
6
7
 
7
- import { z } from 'zod';
8
+ import { z } from 'zod/v4';
8
9
 
9
10
  import { kStringLiteral } from './constant.js';
10
11
 
11
12
  /**
12
13
  * Metadata stored in Zod registry
13
- *
14
- * @knipignore
15
14
  */
16
- export type BupkisMeta = z.infer<typeof BupkisRegistrySchema>;
15
+ type BupkisMeta = z.infer<typeof BupkisRegistrySchema>;
17
16
 
18
17
  /**
19
18
  * Zod metadata registry for Bupkis