bupkis 0.0.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.
- package/CHANGELOG.md +20 -0
- package/LICENSE.md +55 -0
- package/README.md +170 -0
- package/package.json +164 -0
- package/src/api.ts +149 -0
- package/src/assertion/assertion-async.ts +203 -0
- package/src/assertion/assertion-sync.ts +351 -0
- package/src/assertion/assertion-types.ts +964 -0
- package/src/assertion/assertion.ts +234 -0
- package/src/assertion/create.ts +226 -0
- package/src/assertion/impl/README.md +13 -0
- package/src/assertion/impl/async.ts +284 -0
- package/src/assertion/impl/index.ts +2 -0
- package/src/assertion/impl/sync-basic.ts +111 -0
- package/src/assertion/impl/sync-collection.ts +108 -0
- package/src/assertion/impl/sync-esoteric.ts +25 -0
- package/src/assertion/impl/sync-parametric.ts +488 -0
- package/src/assertion/impl/sync.ts +29 -0
- package/src/assertion/index.ts +15 -0
- package/src/assertion/slotify.ts +89 -0
- package/src/bootstrap.ts +55 -0
- package/src/constant.ts +37 -0
- package/src/error.ts +47 -0
- package/src/expect.ts +364 -0
- package/src/guards.ts +223 -0
- package/src/index.ts +29 -0
- package/src/metadata.ts +60 -0
- package/src/schema.md +15 -0
- package/src/schema.ts +464 -0
- package/src/types.ts +159 -0
- package/src/use.ts +63 -0
- package/src/util.ts +264 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core assertion class and parsing engine.
|
|
3
|
+
*
|
|
4
|
+
* This module implements the main `Assertion` class which handles parsing,
|
|
5
|
+
* validation, and execution of assertions. It provides the foundational
|
|
6
|
+
* infrastructure for converting assertion parts into executable validation
|
|
7
|
+
* logic with comprehensive error handling and type safety.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import Debug from 'debug';
|
|
13
|
+
import slug from 'slug';
|
|
14
|
+
import { type ArrayValues } from 'type-fest';
|
|
15
|
+
import { inspect } from 'util';
|
|
16
|
+
import { z } from 'zod/v4';
|
|
17
|
+
|
|
18
|
+
import { kStringLiteral } from '../constant.js';
|
|
19
|
+
import { AssertionError } from '../error.js';
|
|
20
|
+
import { BupkisRegistry } from '../metadata.js';
|
|
21
|
+
import {
|
|
22
|
+
type Assertion,
|
|
23
|
+
type AssertionImpl,
|
|
24
|
+
type AssertionParts,
|
|
25
|
+
type AssertionSlots,
|
|
26
|
+
type ParsedResult,
|
|
27
|
+
type ParsedValues,
|
|
28
|
+
} from './assertion-types.js';
|
|
29
|
+
|
|
30
|
+
const debug = Debug('bupkis:assertion');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Modified charmap for {@link slug} to use underscores to replace hyphens (and
|
|
34
|
+
* for hyphens to replace everything else that needs replacing).
|
|
35
|
+
*
|
|
36
|
+
* @see {@link BupkisAssertion.generateUniqueId} for usage
|
|
37
|
+
*/
|
|
38
|
+
const SLUG_CHARMAP = { ...slug.charmap, '-': '_' };
|
|
39
|
+
|
|
40
|
+
export abstract class BupkisAssertion<
|
|
41
|
+
Parts extends AssertionParts,
|
|
42
|
+
Impl extends AssertionImpl<Parts>,
|
|
43
|
+
Slots extends AssertionSlots<Parts>,
|
|
44
|
+
> implements Assertion<Parts, Impl, Slots>
|
|
45
|
+
{
|
|
46
|
+
readonly id: string;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
readonly parts: Parts,
|
|
50
|
+
readonly slots: Slots,
|
|
51
|
+
readonly impl: Impl,
|
|
52
|
+
) {
|
|
53
|
+
this.id = this.generateAssertionId();
|
|
54
|
+
debug('Created assertion %s', this);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parses raw arguments synchronously against this `Assertion`'s Slots to
|
|
59
|
+
* determine if they match this `Assertion`.
|
|
60
|
+
*
|
|
61
|
+
* @param args Raw arguments provided to `expect()`
|
|
62
|
+
* @returns Result of parsing attempt
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @returns String representation
|
|
67
|
+
*/
|
|
68
|
+
public toString(): string {
|
|
69
|
+
const expand = (zodType: z.core.$ZodType | z.ZodType): string => {
|
|
70
|
+
const def = 'def' in zodType ? zodType.def : zodType._zod.def;
|
|
71
|
+
switch (def.type) {
|
|
72
|
+
case 'custom': {
|
|
73
|
+
const meta = BupkisRegistry.get(zodType);
|
|
74
|
+
if (meta?.name) {
|
|
75
|
+
// our name
|
|
76
|
+
return `{${meta.name}}`;
|
|
77
|
+
} else if ('Class' in zodType._zod.bag) {
|
|
78
|
+
// internal Zod class name. will probably break.
|
|
79
|
+
return `{${(zodType._zod.bag.Class as new (...args: any[]) => any).name}}`;
|
|
80
|
+
}
|
|
81
|
+
return '{custom}';
|
|
82
|
+
}
|
|
83
|
+
case 'default':
|
|
84
|
+
return `{${expand((def as z.core.$ZodDefaultDef).innerType)}}`;
|
|
85
|
+
case 'enum':
|
|
86
|
+
return `${Object.keys((def as z.core.$ZodEnumDef<any>).entries as Record<PropertyKey, unknown>).join(' / ')}`;
|
|
87
|
+
case 'intersection':
|
|
88
|
+
return `${expand((def as z.core.$ZodIntersectionDef<z.core.$ZodType>).left)} & ${expand((def as z.core.$ZodIntersectionDef<z.core.$ZodType>).right)}`;
|
|
89
|
+
case 'literal':
|
|
90
|
+
return (def as z.core.$ZodLiteralDef<any>).values
|
|
91
|
+
.map((value) => `'${value}'`)
|
|
92
|
+
.join(' / ');
|
|
93
|
+
case 'map':
|
|
94
|
+
return `{Map<${expand((def as z.core.$ZodMapDef).keyType)}, ${expand((def as z.core.$ZodMapDef).valueType)}>`;
|
|
95
|
+
case 'nonoptional':
|
|
96
|
+
return `${expand((def as z.core.$ZodNonOptionalDef).innerType)}!`;
|
|
97
|
+
case 'nullable':
|
|
98
|
+
return `${expand((def as z.core.$ZodNullableDef).innerType)}? | null`;
|
|
99
|
+
case 'optional':
|
|
100
|
+
return `${expand((def as z.core.$ZodOptionalDef).innerType)}?`;
|
|
101
|
+
case 'record':
|
|
102
|
+
return `{Record<${expand((def as z.core.$ZodRecordDef).keyType)}, ${expand((def as z.core.$ZodRecordDef).valueType)}>`;
|
|
103
|
+
case 'set':
|
|
104
|
+
return `{Set<${expand((def as z.core.$ZodSetDef).valueType)}>`;
|
|
105
|
+
|
|
106
|
+
case 'tuple':
|
|
107
|
+
return `[${(def as z.core.$ZodTupleDef).items.map(expand).join(', ')}]`;
|
|
108
|
+
case 'union':
|
|
109
|
+
return (
|
|
110
|
+
(def as z.core.$ZodUnionDef<any>).options as z.core.$ZodType[]
|
|
111
|
+
)
|
|
112
|
+
.map(expand)
|
|
113
|
+
.join(' | ');
|
|
114
|
+
default:
|
|
115
|
+
return `{${def.type}}`;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
return `"${this.slots.map(expand).join(' ')}"`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected maybeParseValuesArgMismatch<Args extends readonly unknown[]>(
|
|
122
|
+
args: Args,
|
|
123
|
+
): ParsedResult<Parts> | undefined {
|
|
124
|
+
if (this.slots.length !== args.length) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* TODO: Fix the return types here. This is all sorts of confusing.
|
|
133
|
+
*
|
|
134
|
+
* @param slot Slot to check
|
|
135
|
+
* @param slotIndex Index of slot
|
|
136
|
+
* @param rawArg Raw argument
|
|
137
|
+
* @returns
|
|
138
|
+
*/
|
|
139
|
+
protected parseSlotForLiteral<Slot extends ArrayValues<Slots>>(
|
|
140
|
+
slot: Slot,
|
|
141
|
+
slotIndex: number,
|
|
142
|
+
rawArg: unknown,
|
|
143
|
+
): boolean | ParsedResult<Parts> {
|
|
144
|
+
const meta = BupkisRegistry.get(slot) ?? {};
|
|
145
|
+
// our branded literal slots are also tagged in meta for runtime
|
|
146
|
+
if (kStringLiteral in meta) {
|
|
147
|
+
if ('value' in meta) {
|
|
148
|
+
if (rawArg !== meta.value) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
} else if ('values' in meta) {
|
|
154
|
+
const allowed = meta.values as readonly string[];
|
|
155
|
+
if (!allowed.includes(`${rawArg}`)) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
/* c8 ignore next */
|
|
162
|
+
throw new TypeError(
|
|
163
|
+
`Invalid metadata for slot ${slotIndex} with value ${inspect(rawArg)}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Translates a {@link z.ZodError} into an {@link AssertionError} with a
|
|
173
|
+
* human-friendly message.
|
|
174
|
+
*
|
|
175
|
+
* @remarks
|
|
176
|
+
* This does not handle parameterized assertions with more than one parameter
|
|
177
|
+
* too cleanly; it's unclear how a test runner would display the expected
|
|
178
|
+
* values. This will probably need a fix in the future.
|
|
179
|
+
* @param stackStartFn The function to start the stack trace from
|
|
180
|
+
* @param zodError The original `ZodError`
|
|
181
|
+
* @param values Values which caused the error
|
|
182
|
+
* @returns New `AssertionError`
|
|
183
|
+
*/
|
|
184
|
+
protected translateZodError(
|
|
185
|
+
stackStartFn: (...args: any[]) => any,
|
|
186
|
+
zodError: z.ZodError,
|
|
187
|
+
...values: ParsedValues<Parts>
|
|
188
|
+
): AssertionError {
|
|
189
|
+
const flat = z.flattenError(zodError);
|
|
190
|
+
|
|
191
|
+
let pretty = flat.formErrors.join('; ');
|
|
192
|
+
for (const [keypath, errors] of Object.entries(flat.fieldErrors)) {
|
|
193
|
+
pretty += `; ${keypath}: ${(errors as unknown[]).join('; ')}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const [actual, ...expected] = values as unknown as [unknown, ...unknown[]];
|
|
197
|
+
|
|
198
|
+
return new AssertionError({
|
|
199
|
+
actual,
|
|
200
|
+
expected: expected.length === 1 ? expected[0] : expected,
|
|
201
|
+
message: `Assertion ${this} failed: ${pretty}`,
|
|
202
|
+
operator: `${this}`,
|
|
203
|
+
stackStartFn,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Generates a unique¹ ID for this assertion by combining content, structure,
|
|
209
|
+
* and type information.
|
|
210
|
+
*
|
|
211
|
+
* - `s` is slot count
|
|
212
|
+
* - `p` is part count
|
|
213
|
+
*
|
|
214
|
+
* Slugifies the string representation of the assertion. Does not collapse
|
|
215
|
+
* adjacent hyphens, as hyphens are significant in phrase literals.
|
|
216
|
+
*
|
|
217
|
+
* @remarks
|
|
218
|
+
* ¹: "Unique" here means "unique enough" for practical purposes. This is not
|
|
219
|
+
* cryptographically unique, nor does it need to be. The goal is to avoid
|
|
220
|
+
* collisions in common scenarios while keeping the ID human-readable.
|
|
221
|
+
* @returns A human-readable unique identifier
|
|
222
|
+
*/
|
|
223
|
+
private generateAssertionId(): string {
|
|
224
|
+
const baseSlug = slug(`${this}`, {
|
|
225
|
+
charmap: SLUG_CHARMAP,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Add structural signature for additional uniqueness
|
|
229
|
+
// Use simple slot count and parts count as differentiators
|
|
230
|
+
const signature = `${this.slots.length}s${this.parts.length}p`;
|
|
231
|
+
|
|
232
|
+
return `${baseSlug}-${signature}`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assertion creation factory functions with type-safe sync/async separation.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the core factory functions for creating both synchronous
|
|
5
|
+
* and asynchronous assertions in the Bupkis assertion framework. It implements
|
|
6
|
+
* a dual-creation pattern where `createAssertion()` creates synchronous-only
|
|
7
|
+
* assertions and `createAsyncAssertion()` creates potentially asynchronous
|
|
8
|
+
* assertions, using branded Zod schema types to enforce compile-time safety and
|
|
9
|
+
* prevent accidental mixing of sync and async implementations.
|
|
10
|
+
*
|
|
11
|
+
* The module supports two primary assertion implementation types:
|
|
12
|
+
*
|
|
13
|
+
* - **Schema-based assertions**: Using {@link z.ZodType Zod schemas} for
|
|
14
|
+
* validation
|
|
15
|
+
* - **Function-based assertions**: Using implementation functions that return
|
|
16
|
+
* boolean, void, or Zod schemas for dynamic validation
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* The factory functions use branded types to distinguish between synchronous
|
|
20
|
+
* and asynchronous schema implementations at compile time. This prevents
|
|
21
|
+
* accidentally passing async schemas to sync assertion creators and vice versa,
|
|
22
|
+
* ensuring type safety throughout the assertion system.
|
|
23
|
+
* @example Creating a synchronous string assertion:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { createAssertion } from './create.js';
|
|
27
|
+
* import { z } from 'zod/v4';
|
|
28
|
+
*
|
|
29
|
+
* const stringAssertion = createAssertion(['to be a string'], z.string());
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Creating an asynchronous Promise resolution assertion:
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { createAsyncAssertion } from './create.js';
|
|
36
|
+
*
|
|
37
|
+
* const promiseAssertion = createAsyncAssertion(
|
|
38
|
+
* ['to resolve'],
|
|
39
|
+
* async (promise) => {
|
|
40
|
+
* try {
|
|
41
|
+
* await promise;
|
|
42
|
+
* return true;
|
|
43
|
+
* } catch {
|
|
44
|
+
* return false;
|
|
45
|
+
* }
|
|
46
|
+
* },
|
|
47
|
+
* );
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example Creating parameterized assertions:
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { createAssertion } from './create.js';
|
|
54
|
+
* import { z } from 'zod/v4';
|
|
55
|
+
*
|
|
56
|
+
* const greaterThanAssertion = createAssertion(
|
|
57
|
+
* [z.number(), 'to be greater than', z.number()],
|
|
58
|
+
* (subject, expected) => subject > expected,
|
|
59
|
+
* );
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @packageDocumentation
|
|
63
|
+
* @see {@link AssertionParts} for assertion part structure
|
|
64
|
+
* @see {@link AssertionSlots} for processed slot definitions
|
|
65
|
+
* @see {@link AssertionImplSync} for synchronous implementation types
|
|
66
|
+
* @see {@link AssertionImplAsync} for asynchronous implementation types
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
import { z } from 'zod/v4';
|
|
70
|
+
|
|
71
|
+
import type {
|
|
72
|
+
AssertionFunctionAsync,
|
|
73
|
+
AssertionFunctionSync,
|
|
74
|
+
AssertionImplAsync,
|
|
75
|
+
AssertionImplFnAsync,
|
|
76
|
+
AssertionImplFnSync,
|
|
77
|
+
AssertionImplSchemaAsync,
|
|
78
|
+
AssertionImplSchemaSync,
|
|
79
|
+
AssertionImplSync,
|
|
80
|
+
AssertionParts,
|
|
81
|
+
AssertionSchemaAsync,
|
|
82
|
+
AssertionSchemaSync,
|
|
83
|
+
AssertionSlots,
|
|
84
|
+
RawAssertionImplSchemaSync,
|
|
85
|
+
} from './assertion-types.js';
|
|
86
|
+
|
|
87
|
+
import { isFunction, isString, isZodType } from '../guards.js';
|
|
88
|
+
import {
|
|
89
|
+
BupkisAssertionFunctionAsync,
|
|
90
|
+
BupkisAssertionSchemaAsync,
|
|
91
|
+
} from './assertion-async.js';
|
|
92
|
+
import {
|
|
93
|
+
BupkisAssertionFunctionSync,
|
|
94
|
+
BupkisAssertionSchemaSync,
|
|
95
|
+
} from './assertion-sync.js';
|
|
96
|
+
import { slotify } from './slotify.js';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create a synchronous `Assertion` from {@link AssertionParts parts} and a
|
|
100
|
+
* {@link z.ZodType Zod schema}.
|
|
101
|
+
*
|
|
102
|
+
* @param parts Assertion parts defining the shape of the assertion
|
|
103
|
+
* @param impl Implementation as a Zod schema
|
|
104
|
+
* @returns New `SchemaAssertion` instance
|
|
105
|
+
* @throws {TypeError} Invalid assertion implementation type
|
|
106
|
+
*/
|
|
107
|
+
export function createAssertion<
|
|
108
|
+
const Parts extends AssertionParts,
|
|
109
|
+
Impl extends RawAssertionImplSchemaSync<Parts>,
|
|
110
|
+
Slots extends AssertionSlots<Parts>,
|
|
111
|
+
>(
|
|
112
|
+
parts: Parts,
|
|
113
|
+
impl: Impl,
|
|
114
|
+
): AssertionSchemaSync<Parts, AssertionImplSchemaSync<Parts>, Slots>;
|
|
115
|
+
/**
|
|
116
|
+
* Create a synchronous `Assertion` from {@link AssertionParts parts} and an
|
|
117
|
+
* implementation function.
|
|
118
|
+
*
|
|
119
|
+
* @param parts Assertion parts defining the shape of the assertion
|
|
120
|
+
* @param impl Implementation as a function
|
|
121
|
+
* @returns New `FunctionAssertion` instance
|
|
122
|
+
* @throws {TypeError} Invalid assertion implementation type
|
|
123
|
+
*/
|
|
124
|
+
export function createAssertion<
|
|
125
|
+
const Parts extends AssertionParts,
|
|
126
|
+
Impl extends AssertionImplFnSync<Parts>,
|
|
127
|
+
Slots extends AssertionSlots<Parts>,
|
|
128
|
+
>(parts: Parts, impl: Impl): AssertionFunctionSync<Parts, Impl, Slots>;
|
|
129
|
+
export function createAssertion<
|
|
130
|
+
Impl extends AssertionImplSync<Parts>,
|
|
131
|
+
const Parts extends AssertionParts,
|
|
132
|
+
>(parts: Parts, impl: Impl) {
|
|
133
|
+
if (!Array.isArray(parts)) {
|
|
134
|
+
throw new TypeError('First parameter must be an array');
|
|
135
|
+
}
|
|
136
|
+
if (parts.length === 0) {
|
|
137
|
+
throw new TypeError('At least one value is required for an assertion');
|
|
138
|
+
}
|
|
139
|
+
if (
|
|
140
|
+
!parts.every(
|
|
141
|
+
(part) => isString(part) || Array.isArray(part) || isZodType(part),
|
|
142
|
+
)
|
|
143
|
+
) {
|
|
144
|
+
throw new TypeError('All assertion parts must be strings or Zod schemas');
|
|
145
|
+
}
|
|
146
|
+
if (!impl) {
|
|
147
|
+
throw new TypeError('An assertion implementation is required');
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const slots = slotify<Parts>(parts);
|
|
151
|
+
|
|
152
|
+
if (isZodType(impl)) {
|
|
153
|
+
return new BupkisAssertionSchemaSync(parts, slots, impl);
|
|
154
|
+
} else if (isFunction(impl)) {
|
|
155
|
+
return new BupkisAssertionFunctionSync(parts, slots, impl);
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (err instanceof z.ZodError) {
|
|
159
|
+
throw new TypeError(z.prettifyError(err));
|
|
160
|
+
}
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
throw new TypeError(
|
|
164
|
+
'Assertion implementation must be a function, Zod schema or Zod schema factory',
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create an async `Assertion` from {@link AssertionParts parts} and an async
|
|
169
|
+
* {@link z.ZodType Zod schema}.
|
|
170
|
+
*
|
|
171
|
+
* @param parts Assertion parts defining the shape of the assertion
|
|
172
|
+
* @param impl Implementation as a Zod schema (potentially async)
|
|
173
|
+
* @returns New `BupkisAssertionSchemaAsync` instance
|
|
174
|
+
* @throws {TypeError} Invalid assertion implementation type
|
|
175
|
+
*/
|
|
176
|
+
export function createAsyncAssertion<
|
|
177
|
+
const Parts extends AssertionParts,
|
|
178
|
+
Impl extends AssertionImplSchemaAsync<Parts>,
|
|
179
|
+
Slots extends AssertionSlots<Parts>,
|
|
180
|
+
>(parts: Parts, impl: Impl): AssertionSchemaAsync<Parts, Impl, Slots>;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create an async `Assertion` from {@link AssertionParts parts} and an
|
|
184
|
+
* implementation function.
|
|
185
|
+
*
|
|
186
|
+
* @param parts Assertion parts defining the shape of the assertion
|
|
187
|
+
* @param impl Implementation as a function (potentially async)
|
|
188
|
+
* @returns New `FunctionAssertion` instance
|
|
189
|
+
* @throws {TypeError} Invalid assertion implementation type
|
|
190
|
+
*/
|
|
191
|
+
export function createAsyncAssertion<
|
|
192
|
+
const Parts extends AssertionParts,
|
|
193
|
+
Impl extends AssertionImplFnAsync<Parts>,
|
|
194
|
+
Slots extends AssertionSlots<Parts>,
|
|
195
|
+
>(parts: Parts, impl: Impl): AssertionFunctionAsync<Parts, Impl, Slots>;
|
|
196
|
+
export function createAsyncAssertion<
|
|
197
|
+
const Parts extends AssertionParts,
|
|
198
|
+
Impl extends AssertionImplAsync<Parts>,
|
|
199
|
+
>(parts: Parts, impl: Impl) {
|
|
200
|
+
if (!Array.isArray(parts)) {
|
|
201
|
+
throw new TypeError('First parameter must be an array');
|
|
202
|
+
}
|
|
203
|
+
if (parts.length === 0) {
|
|
204
|
+
throw new TypeError('At least one value is required for an assertion');
|
|
205
|
+
}
|
|
206
|
+
if (
|
|
207
|
+
!parts.every(
|
|
208
|
+
(part) => isString(part) || Array.isArray(part) || isZodType(part),
|
|
209
|
+
)
|
|
210
|
+
) {
|
|
211
|
+
throw new TypeError('All assertion parts must be strings or Zod schemas');
|
|
212
|
+
}
|
|
213
|
+
if (!impl) {
|
|
214
|
+
throw new TypeError('An assertion implementation is required');
|
|
215
|
+
}
|
|
216
|
+
const slots = slotify<Parts>(parts);
|
|
217
|
+
|
|
218
|
+
if (isZodType(impl)) {
|
|
219
|
+
return new BupkisAssertionSchemaAsync(parts, slots, impl);
|
|
220
|
+
} else if (isFunction(impl)) {
|
|
221
|
+
return new BupkisAssertionFunctionAsync(parts, slots, impl);
|
|
222
|
+
}
|
|
223
|
+
throw new TypeError(
|
|
224
|
+
'Assertion implementation must be a function, Zod schema or Zod schema factory',
|
|
225
|
+
);
|
|
226
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Assertion Implementations
|
|
2
|
+
|
|
3
|
+
This dir contains all the built-in assertions that come with _BUPKIS_.
|
|
4
|
+
|
|
5
|
+
They are sorted into the following files:
|
|
6
|
+
|
|
7
|
+
- `async.ts`: Assertions concerning asynchronous functions
|
|
8
|
+
- `sync-basic.ts`: Basic assertions that don't take parameters, e.g. "to be a string", "to be empty", "to be an Error", etc.
|
|
9
|
+
- `sync-collection.ts`: Assertions concerning collections (arrays, `Set`s, `Map`s, objects); may or may not be parametric
|
|
10
|
+
- `sync-esoteric.ts`: Arcane assertions
|
|
11
|
+
- `sync-parametric.ts`: Assertions that take parameters, e.g. "to be greater than", "to be one of", "to have property", etc.
|
|
12
|
+
|
|
13
|
+
All sync assertions are collected and re-exported from `sync.ts`.
|