bupkis 0.1.2 → 0.2.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 (130) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +8 -8
  3. package/dist/commonjs/assertion/assertion-types.d.ts +33 -82
  4. package/dist/commonjs/assertion/assertion-types.d.ts.map +1 -1
  5. package/dist/commonjs/assertion/create.d.ts +5 -33
  6. package/dist/commonjs/assertion/create.d.ts.map +1 -1
  7. package/dist/commonjs/assertion/create.js +17 -6
  8. package/dist/commonjs/assertion/create.js.map +1 -1
  9. package/dist/commonjs/assertion/impl/async.d.ts +5 -5
  10. package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
  11. package/dist/commonjs/assertion/impl/async.js +2 -2
  12. package/dist/commonjs/assertion/impl/async.js.map +1 -1
  13. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +24 -24
  14. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  15. package/dist/commonjs/assertion/impl/sync-parametric.js +3 -3
  16. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  17. package/dist/commonjs/assertion/impl/sync.d.ts +50 -50
  18. package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
  19. package/dist/commonjs/bootstrap.d.ts +71 -61
  20. package/dist/commonjs/bootstrap.d.ts.map +1 -1
  21. package/dist/commonjs/bootstrap.js +20 -10
  22. package/dist/commonjs/bootstrap.js.map +1 -1
  23. package/dist/commonjs/error.d.ts +12 -5
  24. package/dist/commonjs/error.d.ts.map +1 -1
  25. package/dist/commonjs/error.js +19 -4
  26. package/dist/commonjs/error.js.map +1 -1
  27. package/dist/commonjs/expect.d.ts +130 -3
  28. package/dist/commonjs/expect.d.ts.map +1 -1
  29. package/dist/commonjs/expect.js +115 -0
  30. package/dist/commonjs/expect.js.map +1 -1
  31. package/dist/commonjs/guards.d.ts +22 -15
  32. package/dist/commonjs/guards.d.ts.map +1 -1
  33. package/dist/commonjs/guards.js +34 -20
  34. package/dist/commonjs/guards.js.map +1 -1
  35. package/dist/commonjs/index.d.ts +113 -62
  36. package/dist/commonjs/index.d.ts.map +1 -1
  37. package/dist/commonjs/index.js +44 -42
  38. package/dist/commonjs/index.js.map +1 -1
  39. package/dist/commonjs/metadata.d.ts +1 -27
  40. package/dist/commonjs/metadata.d.ts.map +1 -1
  41. package/dist/commonjs/metadata.js +16 -15
  42. package/dist/commonjs/metadata.js.map +1 -1
  43. package/dist/commonjs/schema.d.ts +59 -26
  44. package/dist/commonjs/schema.d.ts.map +1 -1
  45. package/dist/commonjs/schema.js +57 -24
  46. package/dist/commonjs/schema.js.map +1 -1
  47. package/dist/commonjs/types.d.ts +449 -39
  48. package/dist/commonjs/types.d.ts.map +1 -1
  49. package/dist/commonjs/types.js +12 -2
  50. package/dist/commonjs/types.js.map +1 -1
  51. package/dist/commonjs/util.d.ts +7 -0
  52. package/dist/commonjs/util.d.ts.map +1 -1
  53. package/dist/commonjs/util.js +7 -0
  54. package/dist/commonjs/util.js.map +1 -1
  55. package/dist/esm/assertion/assertion-types.d.ts +33 -82
  56. package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
  57. package/dist/esm/assertion/create.d.ts +5 -33
  58. package/dist/esm/assertion/create.d.ts.map +1 -1
  59. package/dist/esm/assertion/create.js +14 -4
  60. package/dist/esm/assertion/create.js.map +1 -1
  61. package/dist/esm/assertion/impl/async.d.ts +5 -5
  62. package/dist/esm/assertion/impl/async.d.ts.map +1 -1
  63. package/dist/esm/assertion/impl/async.js +2 -2
  64. package/dist/esm/assertion/impl/async.js.map +1 -1
  65. package/dist/esm/assertion/impl/sync-parametric.d.ts +24 -24
  66. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  67. package/dist/esm/assertion/impl/sync-parametric.js +3 -3
  68. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  69. package/dist/esm/assertion/impl/sync.d.ts +50 -50
  70. package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
  71. package/dist/esm/bootstrap.d.ts +71 -61
  72. package/dist/esm/bootstrap.d.ts.map +1 -1
  73. package/dist/esm/bootstrap.js +19 -9
  74. package/dist/esm/bootstrap.js.map +1 -1
  75. package/dist/esm/error.d.ts +12 -5
  76. package/dist/esm/error.d.ts.map +1 -1
  77. package/dist/esm/error.js +19 -4
  78. package/dist/esm/error.js.map +1 -1
  79. package/dist/esm/expect.d.ts +130 -3
  80. package/dist/esm/expect.d.ts.map +1 -1
  81. package/dist/esm/expect.js +115 -0
  82. package/dist/esm/expect.js.map +1 -1
  83. package/dist/esm/guards.d.ts +22 -15
  84. package/dist/esm/guards.d.ts.map +1 -1
  85. package/dist/esm/guards.js +29 -13
  86. package/dist/esm/guards.js.map +1 -1
  87. package/dist/esm/index.d.ts +113 -62
  88. package/dist/esm/index.d.ts.map +1 -1
  89. package/dist/esm/index.js +46 -7
  90. package/dist/esm/index.js.map +1 -1
  91. package/dist/esm/metadata.d.ts +1 -27
  92. package/dist/esm/metadata.d.ts.map +1 -1
  93. package/dist/esm/metadata.js +2 -1
  94. package/dist/esm/metadata.js.map +1 -1
  95. package/dist/esm/schema.d.ts +59 -26
  96. package/dist/esm/schema.d.ts.map +1 -1
  97. package/dist/esm/schema.js +57 -24
  98. package/dist/esm/schema.js.map +1 -1
  99. package/dist/esm/types.d.ts +449 -39
  100. package/dist/esm/types.d.ts.map +1 -1
  101. package/dist/esm/types.js +12 -2
  102. package/dist/esm/types.js.map +1 -1
  103. package/dist/esm/util.d.ts +7 -0
  104. package/dist/esm/util.d.ts.map +1 -1
  105. package/dist/esm/util.js +7 -0
  106. package/dist/esm/util.js.map +1 -1
  107. package/package.json +89 -15
  108. package/src/assertion/assertion-types.ts +38 -41
  109. package/src/assertion/create.ts +16 -65
  110. package/src/assertion/impl/async.ts +2 -2
  111. package/src/assertion/impl/sync-parametric.ts +3 -3
  112. package/src/bootstrap.ts +21 -10
  113. package/src/error.ts +21 -4
  114. package/src/expect.ts +269 -18
  115. package/src/guards.ts +29 -51
  116. package/src/index.ts +71 -11
  117. package/src/metadata.ts +3 -4
  118. package/src/schema.ts +59 -26
  119. package/src/types.ts +593 -72
  120. package/src/util.ts +7 -0
  121. package/dist/commonjs/api.d.ts +0 -93
  122. package/dist/commonjs/api.d.ts.map +0 -1
  123. package/dist/commonjs/api.js +0 -8
  124. package/dist/commonjs/api.js.map +0 -1
  125. package/dist/esm/api.d.ts +0 -93
  126. package/dist/esm/api.d.ts.map +0 -1
  127. package/dist/esm/api.js +0 -7
  128. package/dist/esm/api.js.map +0 -1
  129. package/src/api.ts +0 -149
  130. package/src/schema.md +0 -15
package/src/types.ts CHANGED
@@ -1,14 +1,25 @@
1
1
  /**
2
- * Types used throughout _BUPKIS_, mainly related to the
3
- * `expect()`/`expectAsync()` API.
2
+ * Types used throughout _BUPKIS_.
3
+ *
4
+ * May be useful for those building on top of _BUPKIS_.
5
+ *
6
+ * @example
7
+ *
8
+ * ```ts
9
+ * // namespace
10
+ * import { types } from 'bupkis';
11
+ * // subpath import
12
+ * import type * as alsoTypes from 'bupkis/types';
13
+ * ```
4
14
  *
5
15
  * @packageDocumentation
6
16
  */
7
17
 
8
18
  import type {
9
19
  ArrayValues,
10
- NonEmptyTuple,
20
+ TupleToUnion,
11
21
  Constructor as TypeFestConstructor,
22
+ UnionToIntersection,
12
23
  } from 'type-fest';
13
24
  import type { z } from 'zod/v4';
14
25
 
@@ -18,99 +29,501 @@ import type {
18
29
  AnyAsyncAssertions,
19
30
  AnySyncAssertion,
20
31
  AnySyncAssertions,
32
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
+ Assertion,
34
+ AssertionFunctionAsync,
35
+ AssertionFunctionSync,
36
+ AssertionImplFnAsync,
37
+ AssertionImplFnSync,
38
+ AssertionImplSchemaAsync,
39
+ AssertionImplSchemaSync,
21
40
  AssertionPart,
22
41
  AssertionParts,
42
+ AssertionSchemaAsync,
43
+ AssertionSchemaSync,
23
44
  AssertionSlot,
45
+ AssertionSlots,
46
+ BuiltinAsyncAssertions,
47
+ BuiltinSyncAssertions,
24
48
  NoNeverTuple,
25
49
  PhraseLiteral,
26
50
  PhraseLiteralChoice,
27
51
  PhraseLiteralChoiceSlot,
28
52
  PhraseLiteralSlot,
53
+ RawAssertionImplSchemaSync,
29
54
  } from './assertion/assertion-types.js';
30
55
 
31
- import { type Expect, type ExpectAsync } from './api.js';
32
-
33
56
  /**
34
- * Helper type to create negated version of assertion parts. For phrase
35
- * literals, creates "not phrase" version. For phrase arrays, creates negated
36
- * versions of each phrase in the array.
57
+ * Creates a negated version of a tuple of
58
+ * {@link AssertionPart | AssertionParts}.
59
+ *
60
+ * For {@link PhraseLiteral | PhraseLiterals}, creates a
61
+ * {@link Negation | "not" variant}. For
62
+ * {@link PhraseLiteralChoice | PhraseLiteralChoices}, creates negated versions
63
+ * of each `Phrase` in the array.
64
+ *
65
+ * Does not affect Zod schemas.
66
+ *
67
+ * @template Parts Parts containing `PhraseLiterals` or `PhraseLiteralChoices`
68
+ * to negate.
37
69
  */
38
- export type AddNegation<T extends readonly AssertionPart[]> =
39
- T extends readonly [
70
+ export type AddNegation<Parts extends readonly AssertionPart[]> =
71
+ Parts extends readonly [
40
72
  infer First extends AssertionPart,
41
73
  ...infer Rest extends readonly AssertionPart[],
42
74
  ]
43
- ? First extends NonEmptyTuple<string>
75
+ ? First extends PhraseLiteralChoice
44
76
  ? readonly [
45
77
  {
46
- [K in keyof First]: First[K] extends string
78
+ [K in keyof First]: First[K] extends PhraseLiteral
47
79
  ? Negation<First[K]>
48
80
  : never;
49
81
  },
50
82
  ...AddNegation<Rest>,
51
83
  ]
52
- : First extends string
84
+ : First extends PhraseLiteral
53
85
  ? readonly [Negation<First>, ...AddNegation<Rest>]
54
86
  : readonly [First, ...AddNegation<Rest>]
55
87
  : readonly [];
56
88
 
89
+ /**
90
+ * Base set of properties included in both {@link Expect} and {@link ExpectAsync}.
91
+ *
92
+ * @preventExpand
93
+ */
94
+ export interface BaseExpect {
95
+ /**
96
+ * Creates a new synchronous assertion.
97
+ */
98
+ createAssertion: CreateAssertionFn;
99
+
100
+ /**
101
+ * Creates a new asynchronous assertion.
102
+ */
103
+ createAsyncAssertion: CreateAsyncAssertionFn;
104
+ /**
105
+ * Fails immediately with optional `reason`.
106
+ *
107
+ * @param reason Reason for failure
108
+ * @throws {AssertionError}
109
+ */
110
+ fail: FailFn;
111
+ }
112
+
113
+ export type * from './assertion/assertion-types.js';
114
+
115
+ /**
116
+ * The main API as returned by a {@link UseFn}.
117
+ *
118
+ * @template BaseSyncAssertions Base set of synchronous
119
+ * {@link Assertion Assertions}; will be the builtin sync assertions, at
120
+ * minimum)
121
+ * @template BaseAsyncAssertions Base set of asynchronous
122
+ * {@link Assertion Assertions}; will be the builtin async assertions, at
123
+ * minimum)
124
+ * @template ExtendedSyncAssertions Synchronous assertions extracted from
125
+ * `MixedAssertions`
126
+ * @template ExtendedAsyncAssertions Asynchronous assertions extracted from
127
+ * `MixedAssertions`
128
+ */
129
+ export interface Bupkis<
130
+ BaseSyncAssertions extends AnySyncAssertions,
131
+ BaseAsyncAssertions extends AnyAsyncAssertions,
132
+ ExtendedSyncAssertions extends readonly AnySyncAssertion[] = readonly [],
133
+ ExtendedAsyncAssertions extends readonly AnyAsyncAssertion[] = readonly [],
134
+ > {
135
+ /**
136
+ * A new {@link Expect} function which handles {@link ExtendedSyncAssertions}
137
+ * and {@link BaseSyncAssertions}
138
+ */
139
+ expect: Expect<
140
+ Concat<BaseSyncAssertions, ExtendedSyncAssertions>,
141
+ Concat<BaseAsyncAssertions, ExtendedAsyncAssertions>
142
+ >;
143
+ /**
144
+ * A new {@link ExpectAsync} function which handles
145
+ * {@link ExtendedAsyncAssertions} and {@link BaseAsyncAssertions}
146
+ */
147
+ expectAsync: ExpectAsync<
148
+ Concat<BaseAsyncAssertions, ExtendedAsyncAssertions>,
149
+ Concat<BaseSyncAssertions, ExtendedSyncAssertions>
150
+ >;
151
+ /**
152
+ * For composing arrays of assertions, one after another.
153
+ *
154
+ * The _only_ chainable API in <span class="bupkis">Bupkis</span>.
155
+ *
156
+ * @since 0.1.0
157
+ * @example
158
+ *
159
+ * ```ts
160
+ * const { expect } = use([...someAssertions]).use([...otherAssertions]);
161
+ * ```
162
+ */
163
+ use: UseFn<
164
+ Concat<BaseSyncAssertions, ExtendedSyncAssertions>,
165
+ Concat<BaseAsyncAssertions, ExtendedAsyncAssertions>
166
+ >;
167
+ }
168
+
57
169
  /**
58
170
  * Helper type to concatenate two tuples
59
171
  */
60
172
  export type Concat<
61
- A extends readonly unknown[],
62
- B extends readonly unknown[],
63
- > = readonly [...A, ...B];
173
+ TupleA extends readonly unknown[],
174
+ TupleB extends readonly unknown[],
175
+ > = readonly [...TupleA, ...TupleB];
64
176
 
65
177
  /**
66
178
  * A constructor based on {@link TypeFestConstructor type-fest's Constructor}
67
179
  * with a default instance type argument.
68
180
  */
69
181
  export type Constructor<
70
- T = any,
71
- Arguments extends unknown[] = any[],
72
- > = TypeFestConstructor<T, Arguments>;
73
-
74
- export type FilterAsyncAssertions<T extends readonly AnyAssertion[]> =
75
- T extends readonly [
76
- infer S extends AnyAssertion,
77
- ...infer Rest extends readonly AnyAssertion[],
78
- ]
79
- ? S extends AnyAsyncAssertion
80
- ? readonly [S, ...FilterAsyncAssertions<Rest>]
81
- : FilterAsyncAssertions<Rest>
82
- : readonly [];
182
+ Instance = any,
183
+ Args extends unknown[] = any[],
184
+ > = TypeFestConstructor<Instance, Args>;
83
185
 
84
- export type FilterSyncAssertions<T extends readonly AnyAssertion[]> =
85
- T extends readonly [
86
- infer S extends AnyAssertion,
87
- ...infer Rest extends readonly AnyAssertion[],
88
- ]
89
- ? S extends AnySyncAssertion
90
- ? readonly [S, ...FilterSyncAssertions<Rest>]
91
- : FilterSyncAssertions<Rest>
92
- : readonly [];
186
+ /**
187
+ * The main factory function for creating synchronous assertions.
188
+ */
189
+ export interface CreateAssertionFn {
190
+ /**
191
+ * Create a synchronous `Assertion` from {@link AssertionParts parts} and a
192
+ * {@link z.ZodType Zod schema}.
193
+ *
194
+ * @template Parts Parts defining the shape of the assertion, including
195
+ * Phrases and Zod schemas
196
+ * @template Impl Assertion implementation as a Zod schema
197
+ * @template Slots Inferred slots based on the provided `Parts`
198
+ * @returns New `AssertionSchemaSync` object
199
+ */
200
+ <
201
+ const Parts extends AssertionParts,
202
+ Impl extends RawAssertionImplSchemaSync<Parts>,
203
+ Slots extends AssertionSlots<Parts>,
204
+ >(
205
+ parts: Parts,
206
+ impl: Impl,
207
+ ): AssertionSchemaSync<Parts, AssertionImplSchemaSync<Parts>, Slots>;
208
+
209
+ /**
210
+ * Create a synchronous `Assertion` from {@link AssertionParts parts} and an
211
+ * implementation function.
212
+ *
213
+ * @template Parts Parts defining the shape of the assertion, including
214
+ * Phrases and Zod schemas
215
+ * @template Impl Assertion implementation as a function
216
+ * @template Slots Inferred slots based on the provided `Parts`
217
+ * @returns New `AssertionFunctionSync` object
218
+ */
219
+ <
220
+ const Parts extends AssertionParts,
221
+ Impl extends AssertionImplFnSync<Parts>,
222
+ Slots extends AssertionSlots<Parts>,
223
+ >(
224
+ parts: Parts,
225
+ impl: Impl,
226
+ ): AssertionFunctionSync<Parts, Impl, Slots>;
227
+ }
93
228
 
94
229
  /**
95
- * Prepares {@link MapExpectSlots} by injecting `unknown` if the `AssertionParts`
96
- * have no `z.ZodType` in the head position.
230
+ * The main factory function for creating asynchronous assertions.
231
+ */
232
+ export interface CreateAsyncAssertionFn {
233
+ /**
234
+ * Create an async `Assertion` from {@link AssertionParts parts} and an
235
+ * {@link z.ZodType Zod schema}.
236
+ *
237
+ * The Zod schema need not be async itself.
238
+ *
239
+ * @template Parts Parts defining the shape of the assertion, including
240
+ * Phrases and Zod schemas
241
+ * @template Impl Assertion implementation as a Zod schema
242
+ * @template Slots Inferred slots based on the provided `Parts`
243
+ * @returns New `AssertionSchemaAsync` object
244
+ */
245
+ <
246
+ const Parts extends AssertionParts,
247
+ Impl extends AssertionImplSchemaAsync<Parts>,
248
+ Slots extends AssertionSlots<Parts>,
249
+ >(
250
+ parts: Parts,
251
+ impl: Impl,
252
+ ): AssertionSchemaAsync<Parts, Impl, Slots>;
253
+
254
+ /**
255
+ * Create an async `Assertion` from {@link AssertionParts parts} and an
256
+ * implementation function.
257
+ *
258
+ * @template Parts Parts defining the shape of the assertion, including
259
+ * Phrases and Zod schemas
260
+ * @template Impl Assertion implementation as a function
261
+ * @template Slots Inferred slots based on the provided `Parts`
262
+ * @returns New `AssertionFunctionAsync` object
263
+ */
264
+ <
265
+ const Parts extends AssertionParts,
266
+ Impl extends AssertionImplFnAsync<Parts>,
267
+ Slots extends AssertionSlots<Parts>,
268
+ >(
269
+ parts: Parts,
270
+ impl: Impl,
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
+ */
280
+
281
+ /**
282
+ * The main synchronous assertion function.
97
283
  *
98
- * Also filters out `never` from the resulting tuple to guarantee tupleness.
284
+ * Contains properties in {@link ExpectSyncProps}.
99
285
  *
100
- * @remarks
101
- * This is a convenience and I hope it's not too confusing.
286
+ * @template SyncAssertions All synchronous assertions available
287
+ * @template AsyncAssertions All asynchronous assertions available; for use in
288
+ * {@link ExpectSyncProps.use}
289
+ * @expandType ExpectSyncProps
290
+ * @see {@link expect}
102
291
  */
103
- export type InferredExpectSlots<Parts extends AssertionParts> = NoNeverTuple<
104
- Parts extends readonly [infer First extends AssertionPart, ...infer _]
105
- ? First extends PhraseLiteral | PhraseLiteralChoice
106
- ? [unknown, ...MapExpectSlots<Parts>]
107
- : MapExpectSlots<Parts>
108
- : never
109
- >; /**
110
- * Maps `AssertionParts` to the corresponding argument types for `expect` and
111
- * `expectAsync`, as provided by the user.
292
+ export type Expect<
293
+ SyncAssertions extends AnySyncAssertions = BuiltinSyncAssertions,
294
+ AsyncAssertions extends AnyAsyncAssertions = BuiltinAsyncAssertions,
295
+ > = ExpectFunction<SyncAssertions> &
296
+ ExpectSyncProps<SyncAssertions, AsyncAssertions>;
297
+
298
+ /**
299
+ * The main asynchronous assertion function.
300
+ *
301
+ * Contains properties in {@link ExpectAsyncProps}.
302
+ *
303
+ * @template AsyncAssertions All asynchronous assertions available
304
+ * @template SyncAssertions All synchronous assertions available; for use in
305
+ * {@link ExpectAsyncProps.use}
306
+ * @expandType ExpectAsyncProps
307
+ * @see {@link expectAsync}
308
+ */
309
+ export type ExpectAsync<
310
+ AsyncAssertions extends AnyAsyncAssertions = BuiltinAsyncAssertions,
311
+ SyncAssertions extends AnySyncAssertions = BuiltinSyncAssertions,
312
+ > = ExpectAsyncFunction<AsyncAssertions> &
313
+ ExpectAsyncProps<AsyncAssertions, SyncAssertions>;
314
+
315
+ /**
316
+ * The callable function type for asynchronous assertions.
317
+ *
318
+ * This type represents the actual function signature of an async expect
319
+ * function, created by mapping all available assertions to their respective
320
+ * function signatures and combining them using intersection types. Each
321
+ * assertion contributes its own overload to the final function type.
322
+ *
323
+ * The function signatures are derived from the {@link AssertionParts} of each
324
+ * assertion, with parameters that match the expected slots for natural language
325
+ * assertion calls.
326
+ *
327
+ * @example
328
+ *
329
+ * ```typescript
330
+ * // Example function type derived from async assertions
331
+ * const expectAsync: ExpectAsyncFunction<MyAsyncAssertions> = ...;
332
+ * await expectAsync(promise, 'to resolve');
333
+ * await expectAsync(promise, 'to resolve with value satisfying', expectedValue);
334
+ * ```
335
+ *
336
+ * @template T - Array of async assertion objects that define available
337
+ * assertion logic
338
+ * @see {@link ExpectFunction} for the synchronous equivalent
339
+ * @see {@link SlotsFromParts} for how assertion parts are converted to function parameters
340
+ */
341
+ export type ExpectAsyncFunction<
342
+ T extends AnyAsyncAssertions = BuiltinAsyncAssertions,
343
+ > = UnionToIntersection<
344
+ TupleToUnion<{
345
+ [K in keyof T]: T[K] extends AnyAsyncAssertion
346
+ ? (
347
+ ...args: MutableOrReadonly<SlotsFromParts<T[K]['parts']>>
348
+ ) => Promise<void>
349
+ : never;
350
+ }>
351
+ >;
352
+
353
+ /**
354
+ * Properties available on asynchronous expect functions.
355
+ *
356
+ * This interface defines the additional properties and methods that are
357
+ * attached to async expect functions, extending the base expect functionality
358
+ * with async-specific features. These properties provide access to the
359
+ * underlying assertions and enable function composition through the
360
+ * {@link UseFn | use} method.
361
+ *
362
+ * @example
363
+ *
364
+ * ```typescript
365
+ * const expectAsync: ExpectAsync<MyAsyncAssertions> =
366
+ * createExpectAsyncFunction(assertions);
112
367
  *
113
- * Overloads each phrase literal slot with a negated version using a union.
368
+ * // Access the underlying assertions
369
+ * console.log(expectAsync.assertions.length);
370
+ *
371
+ * // Compose with additional assertions
372
+ * const { expectAsync: enhanced } = expectAsync.use(moreAssertions);
373
+ * ```
374
+ *
375
+ * @template AsyncAssertions - Array of async assertion objects available to
376
+ * this expect function
377
+ * @template SyncAssertions - Array of sync assertion objects available for
378
+ * composition via {@link UseFn | use}
379
+ */
380
+ export interface ExpectAsyncProps<
381
+ AsyncAssertions extends AnyAsyncAssertions,
382
+ SyncAssertions extends AnySyncAssertions,
383
+ > extends BaseExpect {
384
+ /**
385
+ * Tuple of all assertions available in this `expect()`.
386
+ *
387
+ * @preventExpand
388
+ */
389
+ assertions: AsyncAssertions;
390
+ /**
391
+ * {@inheritDoc UseFn}
392
+ */
393
+ use: UseFn<SyncAssertions, AsyncAssertions>;
394
+ }
395
+
396
+ export type ExpectFunction<
397
+ SyncAssertions extends AnySyncAssertions = BuiltinSyncAssertions,
398
+ > = UnionToIntersection<
399
+ TupleToUnion<{
400
+ [K in keyof SyncAssertions]: SyncAssertions[K] extends AnySyncAssertion
401
+ ? (
402
+ ...args: MutableOrReadonly<SlotsFromParts<SyncAssertions[K]['parts']>>
403
+ ) => void
404
+ : never;
405
+ }>
406
+ >;
407
+
408
+ /**
409
+ * Properties of {@link expect}.
410
+ */
411
+ export interface ExpectSyncProps<
412
+ SyncAssertions extends AnySyncAssertions,
413
+ AsyncAssertions extends AnyAsyncAssertions,
414
+ > extends BaseExpect {
415
+ /**
416
+ * Tuple of all assertions available in this `expect()`.
417
+ *
418
+ * @preventExpand
419
+ */
420
+ assertions: SyncAssertions;
421
+
422
+ /**
423
+ * Function to add more assertions to this `expect()`, returning a new
424
+ * `expect()` and `expectAsync()` pair with the combined assertions.
425
+ */
426
+ use: UseFn<SyncAssertions, AsyncAssertions>;
427
+ }
428
+
429
+ /**
430
+ * A function which immediately throws an {@link AssertionError}.
431
+ *
432
+ * Member of {@link BaseExpect}.
433
+ *
434
+ * @param reason Optional reason for failure
435
+ * @see {@link fail}
436
+ */
437
+ export type FailFn = (reason?: string) => never;
438
+
439
+ /**
440
+ * Given a mixed array of assertions, filters out only the async assertions.
441
+ */
442
+ /**
443
+ * Given a mixed array of assertions, filters out only the async assertions.
444
+ *
445
+ * This utility type recursively examines each assertion in the input array and
446
+ * constructs a new tuple containing only the asynchronous assertions. It uses
447
+ * conditional types to test whether each assertion extends
448
+ * {@link AnyAsyncAssertion} and includes it in the result if so.
449
+ *
450
+ * Used primarily by {@link UseFn} to separate async assertions from mixed
451
+ * assertion arrays when composing expect functions.
452
+ *
453
+ * @example
454
+ *
455
+ * ```typescript
456
+ * type Mixed = [
457
+ * SyncAssertion1,
458
+ * AsyncAssertion1,
459
+ * SyncAssertion2,
460
+ * AsyncAssertion2,
461
+ * ];
462
+ * type AsyncOnly = FilterAsyncAssertions<Mixed>; // [AsyncAssertion1, AsyncAssertion2]
463
+ * ```
464
+ *
465
+ * @template MixedAssertions - Array that may contain both sync and async
466
+ * assertions
467
+ * @see {@link FilterSyncAssertions} for extracting synchronous assertions
468
+ * @see {@link UseFn} for the primary use case of this type
469
+ */
470
+ export type FilterAsyncAssertions<
471
+ MixedAssertions extends readonly AnyAssertion[],
472
+ > = MixedAssertions extends readonly [
473
+ infer MixedAssertion extends AnyAssertion,
474
+ ...infer Rest extends readonly AnyAssertion[],
475
+ ]
476
+ ? MixedAssertion extends AnyAsyncAssertion
477
+ ? readonly [MixedAssertion, ...FilterAsyncAssertions<Rest>]
478
+ : FilterAsyncAssertions<Rest>
479
+ : readonly [];
480
+
481
+ /**
482
+ * Given a mixed array of assertions, extracts only the synchronous assertions.
483
+ */
484
+ export type FilterSyncAssertions<
485
+ MixedAssertions extends readonly AnyAssertion[],
486
+ > = MixedAssertions extends readonly [
487
+ infer MixedAssertion extends AnyAssertion,
488
+ ...infer Rest extends readonly AnyAssertion[],
489
+ ]
490
+ ? MixedAssertion extends AnySyncAssertion
491
+ ? readonly [MixedAssertion, ...FilterSyncAssertions<Rest>]
492
+ : FilterSyncAssertions<Rest>
493
+ : readonly [];
494
+
495
+ /**
496
+ * Maps AssertionParts to the corresponding argument types for expect and
497
+ * expectAsync functions.
498
+ *
499
+ * This utility type transforms assertion parts into the actual parameter types
500
+ * that users provide when calling expect functions. It handles both phrase
501
+ * literals and Zod schemas, creating appropriate TypeScript types for each
502
+ * slot.
503
+ *
504
+ * For phrase literals, it creates union types that include both the original
505
+ * phrase and its negated version (with "not " prefix). For Zod schemas, it
506
+ * extracts the inferred type. This enables natural language assertions with
507
+ * optional negation support.
508
+ *
509
+ * @remarks
510
+ * This type works recursively through the parts tuple, transforming each part
511
+ * according to its type. The resulting tuple maintains the same structure as
512
+ * the input but with user-facing TypeScript types instead of internal assertion
513
+ * part types.
514
+ * @example
515
+ *
516
+ * ```typescript
517
+ * // Given parts: ['to be a', z.string()]
518
+ * // Results in: ['to be a' | 'not to be a', string]
519
+ * type Slots = MapExpectSlots<['to be a', z.string()]>;
520
+ * // Usage: expect(value, 'to be a', 'hello') or expect(value, 'not to be a', 'hello')
521
+ * ```
522
+ *
523
+ * @template Parts - Tuple of assertion parts to be converted to function
524
+ * parameter types
525
+ * @see {@link SlotsFromParts} for the complete slot transformation including subject injection
526
+ * @see {@link Negation} for how phrase negation is implemented
114
527
  */
115
528
  export type MapExpectSlots<Parts extends readonly AssertionPart[]> =
116
529
  Parts extends readonly [
@@ -134,32 +547,140 @@ export type MapExpectSlots<Parts extends readonly AssertionPart[]> =
134
547
  : readonly [];
135
548
 
136
549
  /**
137
- * Makes tuple types accept both mutable and readonly variants
550
+ * Makes tuple types accept both mutable and readonly variants.
551
+ *
552
+ * This utility type creates a union of both mutable and readonly versions of a
553
+ * tuple type, providing flexibility for function parameters that should accept
554
+ * either variant. This is particularly useful for assertion function parameters
555
+ * where users may pass either `const` arrays (readonly) or regular arrays.
556
+ *
557
+ * The type handles both array types and specific tuple types, creating
558
+ * appropriate unions for each case to maintain type safety while maximizing
559
+ * usability.
560
+ *
561
+ * @example
562
+ *
563
+ * ```typescript
564
+ * type FlexibleArgs = MutableOrReadonly<readonly [string, number]>;
565
+ * // Results in: [string, number] | readonly [string, number]
566
+ *
567
+ * function acceptArgs(args: FlexibleArgs) { ... }
568
+ * acceptArgs(['hello', 42]); // ✓ mutable array
569
+ * acceptArgs(['hello', 42] as const); // ✓ readonly array
570
+ * ```
571
+ *
572
+ * @template Tuple - The readonly tuple type to make flexible
573
+ * @see {@link ExpectFunction} and {@link ExpectAsyncFunction} which use this for parameter flexibility
138
574
  */
139
- export type MutableOrReadonly<T> = T extends readonly (infer U)[]
140
- ? readonly U[] | U[]
141
- : T extends readonly [infer First, ...infer Rest]
142
- ? [First, ...Rest] | readonly [First, ...Rest]
143
- : T;
575
+ export type MutableOrReadonly<Tuple extends readonly unknown[]> =
576
+ Tuple extends readonly (infer Item)[]
577
+ ? Item[] | readonly Item[]
578
+ : Tuple extends readonly [infer First, ...infer Rest]
579
+ ? [First, ...Rest] | readonly [First, ...Rest]
580
+ : Tuple;
144
581
 
145
582
  /**
146
- * The type of a `PhraesLiteral` which is negated, e.g. "not to be"
583
+ * Creates a negated version of a phrase literal by prefixing "not ".
584
+ *
585
+ * This utility type transforms assertion phrases into their negated
586
+ * equivalents, enabling the natural language negation feature in Bupkis
587
+ * assertions. When users provide phrases like "not to be a string", this type
588
+ * helps the system understand and process the negation.
589
+ *
590
+ * The negation is applied at the type level during assertion matching and
591
+ * affects how the assertion logic is executed - negated assertions expect the
592
+ * opposite result.
593
+ *
594
+ * @example
595
+ *
596
+ * ```typescript
597
+ * type Negated = Negation<'to be a string'>; // "not to be a string"
598
+ * type AlsoNegated = Negation<'to equal'>; // "not to equal"
599
+ *
600
+ * // Usage in assertions:
601
+ * expect(42, 'not to be a string'); // Uses negated assertion logic
602
+ * ```
603
+ *
604
+ * @template S - The string literal phrase to be negated
605
+ * @see {@link AddNegation} for applying negation to entire AssertionParts tuples
606
+ * @see {@link MapExpectSlots} for how negation is incorporated into function signatures
147
607
  */
148
- export type Negation<T extends string> = `not ${T}`;
608
+ export type Negation<S extends string> = `not ${S}`;
149
609
 
610
+ /**
611
+ * Converts AssertionParts to complete function parameter types for expect
612
+ * functions.
613
+ *
614
+ * This utility type prepares assertion parts for use as function parameters by
615
+ * applying several transformations:
616
+ *
617
+ * 1. Injects an `unknown` type for the subject parameter if the first part is a
618
+ * phrase literal
619
+ * 2. Maps the remaining parts to their corresponding TypeScript types via
620
+ * {@link MapExpectSlots}
621
+ * 3. Filters out `never` types to ensure a clean tuple structure
622
+ *
623
+ * The subject injection is a key feature - when assertions start with phrases
624
+ * like "to be a string", users still need to provide the subject being tested
625
+ * as the first argument to expect functions.
626
+ *
627
+ * @remarks
628
+ * This type is essential for bridging the gap between assertion definitions and
629
+ * user-facing function signatures. The subject injection ensures that all
630
+ * assertions have a consistent calling pattern regardless of whether they
631
+ * explicitly define a subject parameter.
632
+ * @example
633
+ *
634
+ * ```typescript
635
+ * // Assertion parts: ['to equal', z.string()]
636
+ * // Results in: [unknown, 'to equal' | 'not to equal', string]
637
+ * type Slots = SlotsFromParts<['to equal', z.string()]>;
638
+ *
639
+ * // Usage: expect(subject, 'to equal', 'expected')
640
+ * // expect(subject, 'not to equal', 'unexpected')
641
+ * ```
642
+ *
643
+ * @template Parts - Tuple of assertion parts that define the assertion
644
+ * structure
645
+ * @see {@link MapExpectSlots} for the core slot mapping logic
646
+ * @see {@link NoNeverTuple} for never-type filtering
647
+ */
648
+ export type SlotsFromParts<Parts extends AssertionParts> = NoNeverTuple<
649
+ Parts extends readonly [infer First extends AssertionPart, ...infer _]
650
+ ? First extends PhraseLiteral | PhraseLiteralChoice
651
+ ? [unknown, ...MapExpectSlots<Parts>]
652
+ : MapExpectSlots<Parts>
653
+ : never
654
+ >;
655
+
656
+ /**
657
+ * The type of a `use()` function.
658
+ */
150
659
  export interface UseFn<
151
- T extends AnySyncAssertions,
152
- U extends AnyAsyncAssertions,
660
+ BaseSyncAssertions extends AnySyncAssertions,
661
+ BaseAsyncAssertions extends AnyAsyncAssertions,
153
662
  > {
663
+ /**
664
+ * @template MixedAssertions Mixed set of assertions to add; may include both
665
+ * sync and async assertions
666
+ * @template ExtendedSyncAssertions Synchronous assertions extracted from
667
+ * `MixedAssertions`
668
+ * @template ExtendedAsyncAssertions Asynchronous assertions extracted from
669
+ * `MixedAssertions`
670
+ * @param assertions Array of assertion classes to add
671
+ * @returns New {@link expect} and {@link expectAsync} functions with the
672
+ * combined assertions
673
+ */
154
674
  <
155
- V extends readonly AnyAssertion[],
156
- W extends FilterSyncAssertions<V>,
157
- X extends FilterAsyncAssertions<V>,
675
+ MixedAssertions extends readonly AnyAssertion[],
676
+ ExtendedSyncAssertions extends FilterSyncAssertions<MixedAssertions>,
677
+ ExtendedAsyncAssertions extends FilterAsyncAssertions<MixedAssertions>,
158
678
  >(
159
- assertions: V,
160
- ): {
161
- expect: Expect<Concat<T, W>, Concat<U, X>>;
162
- expectAsync: ExpectAsync<Concat<U, X>, Concat<T, W>>;
163
- use: UseFn<Concat<T, W>, Concat<U, X>>;
164
- };
679
+ assertions: MixedAssertions,
680
+ ): Bupkis<
681
+ BaseSyncAssertions,
682
+ BaseAsyncAssertions,
683
+ ExtendedSyncAssertions,
684
+ ExtendedAsyncAssertions
685
+ >;
165
686
  }