bupkis 0.6.0 → 0.7.2

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 (113) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +33 -30
  3. package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
  4. package/dist/commonjs/assertion/assertion-async.js +4 -11
  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 +3 -13
  8. package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
  9. package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
  10. package/dist/commonjs/assertion/assertion.js +3 -3
  11. package/dist/commonjs/assertion/assertion.js.map +1 -1
  12. package/dist/commonjs/assertion/create.d.ts.map +1 -1
  13. package/dist/commonjs/assertion/create.js +13 -12
  14. package/dist/commonjs/assertion/create.js.map +1 -1
  15. package/dist/commonjs/assertion/impl/async-parametric.d.ts +2 -2
  16. package/dist/commonjs/assertion/impl/async-parametric.d.ts.map +1 -1
  17. package/dist/commonjs/assertion/impl/async-parametric.js +5 -48
  18. package/dist/commonjs/assertion/impl/async-parametric.js.map +1 -1
  19. package/dist/commonjs/assertion/impl/async.d.ts +4 -4
  20. package/dist/commonjs/assertion/impl/sync-collection.js +1 -1
  21. package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
  22. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +2 -2
  23. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  24. package/dist/commonjs/assertion/impl/sync-parametric.js +14 -5
  25. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  26. package/dist/commonjs/assertion/impl/sync.d.ts +4 -4
  27. package/dist/commonjs/assertion/slotify.d.ts.map +1 -1
  28. package/dist/commonjs/assertion/slotify.js +4 -3
  29. package/dist/commonjs/assertion/slotify.js.map +1 -1
  30. package/dist/commonjs/bootstrap.d.ts +4 -4
  31. package/dist/commonjs/constant.js +7 -1
  32. package/dist/commonjs/constant.js.map +1 -1
  33. package/dist/commonjs/error.d.ts +134 -0
  34. package/dist/commonjs/error.d.ts.map +1 -1
  35. package/dist/commonjs/error.js +117 -1
  36. package/dist/commonjs/error.js.map +1 -1
  37. package/dist/commonjs/expect.d.ts +4 -2
  38. package/dist/commonjs/expect.d.ts.map +1 -1
  39. package/dist/commonjs/expect.js +19 -6
  40. package/dist/commonjs/expect.js.map +1 -1
  41. package/dist/commonjs/guards.d.ts.map +1 -1
  42. package/dist/commonjs/guards.js +4 -1
  43. package/dist/commonjs/guards.js.map +1 -1
  44. package/dist/commonjs/index.d.ts +6 -5
  45. package/dist/commonjs/index.d.ts.map +1 -1
  46. package/dist/commonjs/index.js +6 -3
  47. package/dist/commonjs/index.js.map +1 -1
  48. package/dist/commonjs/value-to-schema.d.ts.map +1 -1
  49. package/dist/commonjs/value-to-schema.js +3 -2
  50. package/dist/commonjs/value-to-schema.js.map +1 -1
  51. package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
  52. package/dist/esm/assertion/assertion-async.js +5 -12
  53. package/dist/esm/assertion/assertion-async.js.map +1 -1
  54. package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
  55. package/dist/esm/assertion/assertion-sync.js +7 -17
  56. package/dist/esm/assertion/assertion-sync.js.map +1 -1
  57. package/dist/esm/assertion/assertion.d.ts.map +1 -1
  58. package/dist/esm/assertion/assertion.js +6 -6
  59. package/dist/esm/assertion/assertion.js.map +1 -1
  60. package/dist/esm/assertion/create.d.ts.map +1 -1
  61. package/dist/esm/assertion/create.js +13 -12
  62. package/dist/esm/assertion/create.js.map +1 -1
  63. package/dist/esm/assertion/impl/async-parametric.d.ts +2 -2
  64. package/dist/esm/assertion/impl/async-parametric.d.ts.map +1 -1
  65. package/dist/esm/assertion/impl/async-parametric.js +5 -48
  66. package/dist/esm/assertion/impl/async-parametric.js.map +1 -1
  67. package/dist/esm/assertion/impl/async.d.ts +4 -4
  68. package/dist/esm/assertion/impl/sync-collection.js +1 -1
  69. package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
  70. package/dist/esm/assertion/impl/sync-parametric.d.ts +2 -2
  71. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  72. package/dist/esm/assertion/impl/sync-parametric.js +14 -5
  73. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  74. package/dist/esm/assertion/impl/sync.d.ts +4 -4
  75. package/dist/esm/assertion/slotify.d.ts.map +1 -1
  76. package/dist/esm/assertion/slotify.js +4 -3
  77. package/dist/esm/assertion/slotify.js.map +1 -1
  78. package/dist/esm/bootstrap.d.ts +4 -4
  79. package/dist/esm/constant.js +6 -0
  80. package/dist/esm/constant.js.map +1 -1
  81. package/dist/esm/error.d.ts +134 -0
  82. package/dist/esm/error.d.ts.map +1 -1
  83. package/dist/esm/error.js +110 -1
  84. package/dist/esm/error.js.map +1 -1
  85. package/dist/esm/expect.d.ts +4 -2
  86. package/dist/esm/expect.d.ts.map +1 -1
  87. package/dist/esm/expect.js +22 -9
  88. package/dist/esm/expect.js.map +1 -1
  89. package/dist/esm/guards.d.ts.map +1 -1
  90. package/dist/esm/guards.js +4 -1
  91. package/dist/esm/guards.js.map +1 -1
  92. package/dist/esm/index.d.ts +6 -5
  93. package/dist/esm/index.d.ts.map +1 -1
  94. package/dist/esm/index.js +2 -1
  95. package/dist/esm/index.js.map +1 -1
  96. package/dist/esm/value-to-schema.d.ts.map +1 -1
  97. package/dist/esm/value-to-schema.js +3 -2
  98. package/dist/esm/value-to-schema.js.map +1 -1
  99. package/package.json +17 -21
  100. package/src/assertion/assertion-async.ts +9 -18
  101. package/src/assertion/assertion-sync.ts +12 -24
  102. package/src/assertion/assertion.ts +7 -6
  103. package/src/assertion/create.ts +31 -12
  104. package/src/assertion/impl/async-parametric.ts +7 -49
  105. package/src/assertion/impl/sync-collection.ts +1 -1
  106. package/src/assertion/impl/sync-parametric.ts +19 -5
  107. package/src/assertion/slotify.ts +4 -3
  108. package/src/constant.ts +7 -0
  109. package/src/error.ts +188 -0
  110. package/src/expect.ts +31 -10
  111. package/src/guards.ts +4 -1
  112. package/src/index.ts +2 -1
  113. package/src/value-to-schema.ts +3 -2
@@ -1,9 +1,8 @@
1
- import Debug from 'debug';
2
1
  import { inspect } from 'util';
3
2
  import z from 'zod/v4';
4
3
 
5
4
  import { kStringLiteral } from '../constant.js';
6
- import { AssertionError } from '../error.js';
5
+ import { AssertionError, AssertionImplementationError } from '../error.js';
7
6
  import {
8
7
  isA,
9
8
  isAssertionFailure,
@@ -26,7 +25,6 @@ import {
26
25
  type ParsedValues,
27
26
  } from './assertion-types.js';
28
27
  import { BupkisAssertion } from './assertion.js';
29
- const debug = Debug('bupkis:assertion:async');
30
28
 
31
29
  export abstract class BupkisAssertionAsync<
32
30
  Parts extends AssertionParts,
@@ -68,7 +66,6 @@ export abstract class BupkisAssertionAsync<
68
66
  // unknown/any accept anything
69
67
  // IMPORTANT: do not use a type guard here; it will break inference
70
68
  if (slot.def.type === 'unknown' || slot.def.type === 'any') {
71
- debug('Skipping unknown/any slot validation for arg', arg);
72
69
  parsedValues.push(arg);
73
70
  exactMatch = false;
74
71
  continue;
@@ -143,8 +140,9 @@ export class BupkisAssertionFunctionAsync<
143
140
  } else if (isError(result) && result instanceof z.ZodError) {
144
141
  throw this.fromZodError(result, stackStartFn, parsedValues);
145
142
  } else if (result as unknown) {
146
- throw new TypeError(
143
+ throw new AssertionImplementationError(
147
144
  `Invalid return type from assertion ${this}; expected boolean, ZodType, or AssertionFailure`,
145
+ { result },
148
146
  );
149
147
  }
150
148
  }
@@ -182,19 +180,13 @@ export class BupkisAssertionSchemaAsync<
182
180
  ? parseResult.subjectValidationResult
183
181
  : undefined;
184
182
 
185
- if (cachedValidation) {
186
- debug(
187
- 'Using cached subject validation result from parseValuesAsync for %s',
188
- this,
183
+ if (cachedValidation && !cachedValidation.success) {
184
+ // Subject validation failed during parseValuesAsync, throw the cached error
185
+ throw this.fromZodError(
186
+ cachedValidation.error,
187
+ stackStartFn,
188
+ parsedValues,
189
189
  );
190
- if (!cachedValidation.success) {
191
- // Subject validation failed during parseValuesAsync, throw the cached error
192
- throw this.fromZodError(
193
- cachedValidation.error,
194
- stackStartFn,
195
- parsedValues,
196
- );
197
- }
198
190
  }
199
191
 
200
192
  // Fall back to standard validation if no cached result
@@ -258,7 +250,6 @@ export class BupkisAssertionSchemaAsync<
258
250
 
259
251
  // Standard slot processing for non-optimized cases
260
252
  if (slot.def.type === 'unknown' || slot.def.type === 'any') {
261
- debug('Skipping unknown/any slot validation for arg', arg);
262
253
  parsedValues.push(arg);
263
254
  exactMatch = false;
264
255
  continue;
@@ -6,18 +6,21 @@
6
6
  * @see {@link AssertionSchemaSync} for schema-based assertions
7
7
  */
8
8
 
9
- import Debug from 'debug';
9
+ import createDebug from 'debug';
10
10
  import { inspect } from 'util';
11
11
  import { z } from 'zod/v4';
12
12
 
13
13
  import { kStringLiteral } from '../constant.js';
14
- import { AssertionError } from '../error.js';
14
+ import {
15
+ AssertionError,
16
+ AssertionImplementationError,
17
+ UnexpectedAsyncError,
18
+ } from '../error.js';
15
19
  import {
16
20
  isAssertionFailure,
17
21
  isBoolean,
18
22
  isError,
19
23
  isPromiseLike,
20
- isZodPromise,
21
24
  isZodType,
22
25
  } from '../guards.js';
23
26
  import { BupkisRegistry } from '../metadata.js';
@@ -36,7 +39,7 @@ import {
36
39
  } from './assertion-types.js';
37
40
  import { BupkisAssertion } from './assertion.js';
38
41
 
39
- const debug = Debug('bupkis:assertion:sync');
42
+ const debug = createDebug('bupkis:assertion:sync');
40
43
 
41
44
  /**
42
45
  * Abstract class for synchronous assertions.
@@ -106,17 +109,11 @@ export abstract class BupkisAssertionSync<
106
109
  // unknown/any accept anything
107
110
  // IMPORTANT: do not use a type guard here
108
111
  if (slot.def.type === 'unknown' || slot.def.type === 'any') {
109
- // debug('Skipping unknown/any slot validation for arg', arg);
110
112
  parsedValues.push(arg);
111
113
  exactMatch = false;
112
114
  continue;
113
115
  }
114
- // low-effort check
115
- if (isZodPromise(slot)) {
116
- throw new TypeError(
117
- `${this} expects a Promise for slot ${i}; use expectAsync() instead of expect()`,
118
- );
119
- }
116
+
120
117
  const result = slot.safeParse(arg);
121
118
  if (!result.success) {
122
119
  return {
@@ -156,10 +153,10 @@ export class BupkisAssertionFunctionSync<
156
153
  if (isPromiseLike(result)) {
157
154
  // Avoid unhandled promise rejection
158
155
  Promise.resolve(result).catch((err) => {
159
- debug(`Ate unhandled rejection from assertion %s: %O`, this, err);
156
+ debug(`⚠️ Ate unhandled rejection from assertion %s: %O`, this, err);
160
157
  });
161
158
 
162
- throw new TypeError(
159
+ throw new UnexpectedAsyncError(
163
160
  `Assertion ${this} returned a Promise; use expectAsync() instead of expect()`,
164
161
  );
165
162
  }
@@ -183,8 +180,9 @@ export class BupkisAssertionFunctionSync<
183
180
  } else if (isError(result) && result instanceof z.ZodError) {
184
181
  throw this.fromZodError(result, stackStartFn, parsedValues);
185
182
  } else if (result as unknown) {
186
- throw new TypeError(
183
+ throw new AssertionImplementationError(
187
184
  `Invalid return type from assertion ${this}; expected boolean, ZodType, or AssertionFailure`,
185
+ { result },
188
186
  );
189
187
  }
190
188
  }
@@ -223,10 +221,6 @@ export class BupkisAssertionSchemaSync<
223
221
  : undefined;
224
222
 
225
223
  if (cachedValidation) {
226
- debug(
227
- 'Using cached subject validation result from parseValues for %s',
228
- this,
229
- );
230
224
  if (!cachedValidation.success) {
231
225
  // Subject validation failed during parseValues, throw the cached error
232
226
  throw this.fromZodError(
@@ -293,17 +287,11 @@ export class BupkisAssertionSchemaSync<
293
287
 
294
288
  // Standard slot processing for non-optimized cases
295
289
  if (slot.def.type === 'unknown' || slot.def.type === 'any') {
296
- debug('Skipping unknown/any slot validation for arg', arg);
297
290
  parsedValues.push(arg);
298
291
  exactMatch = false;
299
292
  continue;
300
293
  }
301
294
 
302
- if (isZodPromise(slot)) {
303
- throw new TypeError(
304
- `${this} expects a Promise for slot ${i}; use expectAsync() instead of expect()`,
305
- );
306
- }
307
295
  const result = slot.safeParse(arg);
308
296
  if (!result.success) {
309
297
  return {
@@ -9,14 +9,14 @@
9
9
  * @packageDocumentation
10
10
  */
11
11
 
12
- import Debug from 'debug';
12
+ import createDebug from 'debug';
13
13
  import slug from 'slug';
14
14
  import { type ArrayValues } from 'type-fest';
15
15
  import { inspect } from 'util';
16
16
  import { z } from 'zod/v4';
17
17
 
18
18
  import { kStringLiteral } from '../constant.js';
19
- import { AssertionError } from '../error.js';
19
+ import { AssertionError, InvalidMetadataError } from '../error.js';
20
20
  import { BupkisRegistry } from '../metadata.js';
21
21
  import {
22
22
  type Assertion,
@@ -28,7 +28,7 @@ import {
28
28
  type ParsedValues,
29
29
  } from './assertion-types.js';
30
30
 
31
- const debug = Debug('bupkis:assertion');
31
+ const debug = createDebug('bupkis:assertion');
32
32
 
33
33
  /**
34
34
  * Modified charmap for {@link slug} to use underscores to replace hyphens (`-`;
@@ -64,7 +64,7 @@ export abstract class BupkisAssertion<
64
64
  readonly impl: Impl,
65
65
  ) {
66
66
  this.id = this.generateAssertionId();
67
- debug('Created assertion %s', this);
67
+ debug('Created assertion %s', this);
68
68
  }
69
69
 
70
70
  public metadata(): AssertionMetadata | undefined {
@@ -96,7 +96,7 @@ export abstract class BupkisAssertion<
96
96
  : 'custom';
97
97
  } catch (err) {
98
98
  debug(
99
- `Warning: Unable to extract custom class name from Zod type(did Zod's API change?): ${err}`,
99
+ `⚠️ WARNING: Unable to extract custom class name from Zod type(did Zod's API change?): ${err}`,
100
100
  );
101
101
  repr = 'custom';
102
102
  }
@@ -251,8 +251,9 @@ export abstract class BupkisAssertion<
251
251
  }
252
252
  } else {
253
253
  /* c8 ignore next */
254
- throw new TypeError(
254
+ throw new InvalidMetadataError(
255
255
  `Invalid metadata for slot ${slotIndex} with value ${inspect(rawArg)}`,
256
+ { metadata: meta },
256
257
  );
257
258
  }
258
259
  return true;
@@ -75,6 +75,7 @@ import type {
75
75
  AssertionParts,
76
76
  } from './assertion-types.js';
77
77
 
78
+ import { AssertionImplementationError } from '../error.js';
78
79
  import { isFunction, isString, isZodType } from '../guards.js';
79
80
  import {
80
81
  BupkisAssertionFunctionAsync,
@@ -107,20 +108,26 @@ export const createAssertion: CreateAssertionFn = <
107
108
  metadata?: AssertionMetadata,
108
109
  ) => {
109
110
  if (!Array.isArray(parts)) {
110
- throw new TypeError('First parameter must be an array');
111
+ throw new AssertionImplementationError('First parameter must be an array');
111
112
  }
112
113
  if (parts.length === 0) {
113
- throw new TypeError('At least one value is required for an assertion');
114
+ throw new AssertionImplementationError(
115
+ 'At least one value is required for an assertion',
116
+ );
114
117
  }
115
118
  if (
116
119
  !parts.every(
117
120
  (part) => isString(part) || Array.isArray(part) || isZodType(part),
118
121
  )
119
122
  ) {
120
- throw new TypeError('All assertion parts must be strings or Zod schemas');
123
+ throw new AssertionImplementationError(
124
+ 'All assertion parts must be strings or Zod schemas',
125
+ );
121
126
  }
122
127
  if (!impl) {
123
- throw new TypeError('An assertion implementation is required');
128
+ throw new AssertionImplementationError(
129
+ 'An assertion implementation is required',
130
+ );
124
131
  }
125
132
  try {
126
133
  const slots = slotify<Parts>(parts);
@@ -140,11 +147,17 @@ export const createAssertion: CreateAssertionFn = <
140
147
  }
141
148
  } catch (err) {
142
149
  if (err instanceof z.ZodError) {
143
- throw new TypeError(z.prettifyError(err));
150
+ throw new AssertionImplementationError(
151
+ `Failed to slotify assertion parts: ${z.prettifyError(err)}`,
152
+ { cause: err },
153
+ );
144
154
  }
145
- throw err;
155
+ throw new AssertionImplementationError(
156
+ `Failed to slotify assertion parts: ${err}`,
157
+ { cause: err },
158
+ );
146
159
  }
147
- throw new TypeError(
160
+ throw new AssertionImplementationError(
148
161
  'Assertion implementation must be a function, Zod schema or Zod schema factory',
149
162
  );
150
163
  };
@@ -164,20 +177,26 @@ export const createAsyncAssertion: CreateAsyncAssertionFn = <
164
177
  metadata?: AssertionMetadata,
165
178
  ) => {
166
179
  if (!Array.isArray(parts)) {
167
- throw new TypeError('First parameter must be an array');
180
+ throw new AssertionImplementationError('First parameter must be an array');
168
181
  }
169
182
  if (parts.length === 0) {
170
- throw new TypeError('At least one value is required for an assertion');
183
+ throw new AssertionImplementationError(
184
+ 'At least one value is required for an assertion',
185
+ );
171
186
  }
172
187
  if (
173
188
  !parts.every(
174
189
  (part) => isString(part) || Array.isArray(part) || isZodType(part),
175
190
  )
176
191
  ) {
177
- throw new TypeError('All assertion parts must be strings or Zod schemas');
192
+ throw new AssertionImplementationError(
193
+ 'All assertion parts must be strings or Zod schemas',
194
+ );
178
195
  }
179
196
  if (!impl) {
180
- throw new TypeError('An assertion implementation is required');
197
+ throw new AssertionImplementationError(
198
+ 'An assertion implementation is required',
199
+ );
181
200
  }
182
201
  const slots = slotify<Parts>(parts);
183
202
 
@@ -200,7 +219,7 @@ export const createAsyncAssertion: CreateAsyncAssertionFn = <
200
219
  }
201
220
  return assertion;
202
221
  }
203
- throw new TypeError(
222
+ throw new AssertionImplementationError(
204
223
  'Assertion implementation must be a function, Zod schema or Zod schema factory',
205
224
  );
206
225
  };
@@ -16,6 +16,7 @@
16
16
  import { inspect } from 'node:util';
17
17
  import { z } from 'zod/v4';
18
18
 
19
+ import { InvalidSchemaError } from '../../error.js';
19
20
  import { isA, isNonNullObject, isString } from '../../guards.js';
20
21
  import {
21
22
  ConstructibleSchema,
@@ -266,8 +267,9 @@ export const functionRejectWithErrorSatisfyingAssertion = createAsyncAssertion(
266
267
  }
267
268
  /* c8 ignore next 5 */
268
269
  if (!schema) {
269
- throw new TypeError(
270
+ throw new InvalidSchemaError(
270
271
  `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
272
+ { schema: param },
271
273
  );
272
274
  }
273
275
 
@@ -334,8 +336,9 @@ export const promiseRejectWithErrorSatisfyingAssertion = createAsyncAssertion(
334
336
  }
335
337
  /* c8 ignore next 5 */
336
338
  if (!schema) {
337
- throw new TypeError(
339
+ throw new InvalidSchemaError(
338
340
  `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
341
+ { schema: param },
339
342
  );
340
343
  }
341
344
 
@@ -391,30 +394,8 @@ export const promiseFulfillWithValueSatisfyingAssertion = createAsyncAssertion(
391
394
  )}`,
392
395
  };
393
396
  }
394
- let schema: undefined | z.ZodType;
395
- // TODO: can valueToSchema handle the first two conditional branches?
396
- if (isString(param)) {
397
- schema = z
398
- .looseObject({
399
- message: z.coerce.string().pipe(z.literal(param)),
400
- })
401
- .or(z.coerce.string().pipe(z.literal(param)));
402
- } else if (isA(param, RegExp)) {
403
- schema = z
404
- .looseObject({
405
- message: z.coerce.string().regex(param),
406
- })
407
- .or(z.coerce.string().regex(param));
408
- } else if (isNonNullObject(param)) {
409
- schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
410
- }
411
- /* c8 ignore next 5 */
412
- if (!schema) {
413
- throw new TypeError(
414
- `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
415
- );
416
- }
417
397
 
398
+ const schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
418
399
  const result = schema.safeParse(value);
419
400
  if (!result.success) {
420
401
  return result.error;
@@ -468,30 +449,7 @@ export const functionFulfillWithValueSatisfyingAssertion = createAsyncAssertion(
468
449
  };
469
450
  }
470
451
 
471
- let schema: undefined | z.ZodType;
472
- // TODO: can valueToSchema handle the first two conditional branches?
473
- if (isString(param)) {
474
- schema = z
475
- .looseObject({
476
- message: z.coerce.string().pipe(z.literal(param)),
477
- })
478
- .or(z.coerce.string().pipe(z.literal(param)));
479
- } else if (isA(param, RegExp)) {
480
- schema = z
481
- .looseObject({
482
- message: z.coerce.string().regex(param),
483
- })
484
- .or(z.coerce.string().regex(param));
485
- } else if (isNonNullObject(param)) {
486
- schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
487
- }
488
- /* c8 ignore next 5 */
489
- if (!schema) {
490
- throw new TypeError(
491
- `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
492
- );
493
- }
494
-
452
+ const schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
495
453
  const result = schema.safeParse(value);
496
454
  if (!result.success) {
497
455
  return result.error;
@@ -462,7 +462,7 @@ export const objectSizeAssertion = createAssertion(
462
462
  const actual = Object.keys(subject).length;
463
463
  if (actual !== expectedSize) {
464
464
  return {
465
- actual: actual,
465
+ actual,
466
466
  expected: expectedSize,
467
467
  message: `Expected object to have ${expectedSize} keys, but it has ${actual} keys`,
468
468
  };
@@ -16,6 +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
20
  import { isA, isError, isNonNullObject, isString } from '../../guards.js';
20
21
  import {
21
22
  ArrayLikeSchema,
@@ -146,7 +147,7 @@ export const typeOfAssertion = createAssertion(
146
147
  return z.instanceof(WeakSet);
147
148
  // c8 ignore next 2
148
149
  default:
149
- throw new TypeError(`Unknown type: "${type}"`);
150
+ throw new BupkisError(`Unknown "type": "${type}"`);
150
151
  }
151
152
  },
152
153
  );
@@ -274,7 +275,7 @@ export const numberCloseToAssertion = createAssertion(
274
275
  if (diff > tolerance) {
275
276
  return {
276
277
  actual: subject,
277
- expected: expected,
278
+ expected,
278
279
  message: `Expected ${subject} to be close to ${expected} (within ${tolerance}), but difference was ${diff}`,
279
280
  };
280
281
  }
@@ -748,7 +749,10 @@ export const functionThrowsMatchingAssertion = createAssertion(
748
749
  const schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
749
750
  return schema.safeParse(error).success;
750
751
  } else {
751
- throw new TypeError(`Invalid parameter schema: ${inspect(param)}`);
752
+ throw new InvalidSchemaError(
753
+ `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
754
+ { schema: param },
755
+ );
752
756
  }
753
757
  },
754
758
  );
@@ -819,8 +823,9 @@ export const functionThrowsTypeSatisfyingAssertion = createAssertion(
819
823
  schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
820
824
  }
821
825
  if (!schema) {
822
- throw new TypeError(
826
+ throw new InvalidSchemaError(
823
827
  `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
828
+ { schema: param },
824
829
  );
825
830
  }
826
831
 
@@ -892,8 +897,17 @@ export const stringMatchesAssertion = createAssertion(
892
897
  * @group Parametric Assertions (Sync)
893
898
  */
894
899
  export const objectSatisfiesAssertion = createAssertion(
895
- [z.looseObject({}).nonoptional(), ['to satisfy', 'to be like'], z.any()],
900
+ [
901
+ z.looseObject({}).nonoptional(),
902
+ ['to satisfy', 'to be like', 'satisfies'],
903
+ z.any(),
904
+ ],
896
905
  (_subject, shape) => valueToSchema(shape, valueToSchemaOptionsForSatisfies),
906
+ {
907
+ anchor: 'object-to-satisfy-any',
908
+ category: 'object',
909
+ redirect: 'satisfies',
910
+ },
897
911
  );
898
912
 
899
913
  /**
@@ -14,6 +14,7 @@ import { z } from 'zod/v4';
14
14
  import type { AssertionParts, AssertionSlots } from './assertion-types.js';
15
15
 
16
16
  import { kStringLiteral } from '../constant.js';
17
+ import { AssertionImplementationError } from '../error.js';
17
18
  import {
18
19
  isPhraseLiteral,
19
20
  isPhraseLiteralChoice,
@@ -43,7 +44,7 @@ export const slotify = <const Parts extends AssertionParts>(
43
44
 
44
45
  if (isPhraseLiteralChoice(part)) {
45
46
  if (part.some((p) => p.startsWith('not '))) {
46
- throw new TypeError(
47
+ throw new AssertionImplementationError(
47
48
  `PhraseLiteralChoice at parts[${index}] must not include phrases starting with "not ": ${inspect(
48
49
  part,
49
50
  )}`,
@@ -60,7 +61,7 @@ export const slotify = <const Parts extends AssertionParts>(
60
61
  );
61
62
  } else if (isPhraseLiteral(part)) {
62
63
  if (part.startsWith('not ')) {
63
- throw new TypeError(
64
+ throw new AssertionImplementationError(
64
65
  `PhraseLiteral at parts[${index}] must not start with "not ": ${inspect(
65
66
  part,
66
67
  )}`,
@@ -77,7 +78,7 @@ export const slotify = <const Parts extends AssertionParts>(
77
78
  );
78
79
  } else {
79
80
  if (!isZodType(part)) {
80
- throw new TypeError(
81
+ throw new AssertionImplementationError(
81
82
  `Expected Zod schema, phrase literal, or phrase literal choice at parts[${index}] but received ${inspect(
82
83
  part,
83
84
  )} (${typeof part})`,
package/src/constant.ts CHANGED
@@ -77,3 +77,10 @@ export const kExpectIt: unique symbol = Symbol('bupkis-expect-it');
77
77
 
78
78
  export const KEYPATH_REGEX =
79
79
  /^[a-zA-Z_$][-a-zA-Z0-9_$]*(?:(?:\.[a-zA-Z_$][-a-zA-Z0-9_$]*)|(?:\[(?:\d+|"[^"]*"|'[^']*')\]))*$/;
80
+
81
+ /**
82
+ * Symbol used to flag a function as a Bupkis-thrown error.
83
+ *
84
+ * @internal
85
+ */
86
+ export const kBupkisError: unique symbol = Symbol('bupkis-error');