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 ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## [0.0.2](https://github.com/boneskull/bupkis/compare/bupkis-v0.0.1...bupkis-v0.0.2) (2025-09-07)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * add repository to package.json ([4d687a5](https://github.com/boneskull/bupkis/commit/4d687a54c4fb34331508011df14fcfd966bf7ad3))
9
+
10
+ ## 0.0.1 (2025-09-07)
11
+
12
+
13
+ ### Features
14
+
15
+ * "initial commit" ([04f234c](https://github.com/boneskull/bupkis/commit/04f234c8f8cea4cef5bae0dc7ccb692eb91d8748))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * remove ref to distfile ([5de9a6b](https://github.com/boneskull/bupkis/commit/5de9a6b6f7bebe3f8898eecc3ae2212e183c3a16))
package/LICENSE.md ADDED
@@ -0,0 +1,55 @@
1
+ # Blue Oak Model License
2
+
3
+ Version 1.0.0
4
+
5
+ ## Purpose
6
+
7
+ This license gives everyone as much permission to work with
8
+ this software as possible, while protecting contributors
9
+ from liability.
10
+
11
+ ## Acceptance
12
+
13
+ In order to receive this license, you must agree to its
14
+ rules. The rules of this license are both obligations
15
+ under that agreement and conditions to your license.
16
+ You must not do anything with this software that triggers
17
+ a rule that you cannot or will not follow.
18
+
19
+ ## Copyright
20
+
21
+ Each contributor licenses you to do everything with this
22
+ software that would otherwise infringe that contributor's
23
+ copyright in it.
24
+
25
+ ## Notices
26
+
27
+ You must ensure that everyone who gets a copy of
28
+ any part of this software from you, with or without
29
+ changes, also gets the text of this license or a link to
30
+ <https://blueoakcouncil.org/license/1.0.0>.
31
+
32
+ ## Excuse
33
+
34
+ If anyone notifies you in writing that you have not
35
+ complied with [Notices](#notices), you can keep your
36
+ license by taking all practical steps to comply within 30
37
+ days after the notice. If you do not do so, your license
38
+ ends immediately.
39
+
40
+ ## Patent
41
+
42
+ Each contributor licenses you to do everything with this
43
+ software that would otherwise infringe any patent claims
44
+ they can license or become able to license.
45
+
46
+ ## Reliability
47
+
48
+ No contributor can revoke this license.
49
+
50
+ ## No Liability
51
+
52
+ **_As far as the law allows, this software comes as is,
53
+ without any warranty or condition, and no contributor
54
+ will be liable to anyone for any damages related to this
55
+ software or this license, under any kind of legal claim._**
package/README.md ADDED
@@ -0,0 +1,170 @@
1
+ <p align="center">
2
+ <img src="https://github.com/boneskull/bupkis/blob/main/assets/bupkis-logo-256.png" width="256px" align="center" alt="BUPKIS logo"/>
3
+ <h1 align="center"><em><span class="federo-caps">BUPKIS<span></em></h1>
4
+ <p align="center">
5
+ A well-typed and easily exensible <em>BDD-style</em> assertion library.
6
+ <br/>
7
+ by <a href="https://github.com/boneskull">@boneskull</a>
8
+ </p>
9
+ </p>
10
+
11
+ ## Motivation
12
+
13
+ Look, I'm ~~old~~ ~~wizened~~ ~~experienced~~ knowledegable and I've written a lot of tests. I've used a lot of assertion libraries. There are ones I prefer and ones I don't.
14
+
15
+ But none of them do quite what _this_ does. The main goals of this library are:
16
+
17
+ - Type safety
18
+ - Dead-simple creation of custom assertions
19
+ - Minimal API surface
20
+
21
+ A chainable API may provide type safety. But it seems to _guarantee_ implementing a custom assertion will be complicated. The API surface is necessarily a combinatoric explosion of methods.
22
+
23
+ > [!WARNING]
24
+ >
25
+ > Because chainable APIs are familiar, you may hate _BUPKIS_ once you see some examples. You don't have to use it, but please: _don't confuse familiarity with usability_.
26
+
27
+ To achieve these goals, I've adopted the following design principles:
28
+
29
+ ### Natural-Language Assertions
30
+
31
+ In `bupkis` (stylized as "_BUPKIS_"), you **don't** write this:
32
+
33
+ ```js
34
+ expect(actual).toEqual(expected);
35
+ ```
36
+
37
+ Instead, you write this:
38
+
39
+ ```js
40
+ expect(actual, 'is', expected);
41
+ // or this
42
+ expect(actual, 'to be', expected);
43
+ // or this
44
+ expect(actual, 'to equal', expected);
45
+ // or even this
46
+ expect(actual, 'equals', expected);
47
+ // or yet another way
48
+ expect(actual, 'is equal to', expected);
49
+ // or believe it or not, even this
50
+ expect(actual, 'to strictly equal', expected);
51
+ ```
52
+
53
+ If Chai wants you to write:
54
+
55
+ ```js
56
+ expect(actual).to.be.a('string');
57
+ ```
58
+
59
+ Then _BUPKIS_ wants you to write:
60
+
61
+ ```js
62
+ expect(actual, 'to be a string');
63
+ // it is tolerant of poor/ironic grammar
64
+ expect(actual, 'to be an string');
65
+ ```
66
+
67
+ Can't remember the string? Did you forget a word or make a typo? Maybe you also forgot _BUPKIS_ is type-safe? You'll get a nice squiggly for your trouble.
68
+
69
+ The "string" part of an expectation is known as a _phrase_. Every expectation will contain, at minimum, one phrase. As you can see from the above example, phrases often have aliases.
70
+
71
+ You can negate just about any phrase:
72
+
73
+ ```js
74
+ expect(actual, 'to be', expected);
75
+ // did they not teach grammar in nerd school??
76
+ expect(actual, 'not is', expected);
77
+
78
+ expect(
79
+ () => throw new TypeError('aww, shucks'),
80
+ 'to throw a',
81
+ TypeError,
82
+ 'not satisfying',
83
+ /gol durn/,
84
+ );
85
+ ```
86
+
87
+ ### Custom Assertions
88
+
89
+ In _BUPKIS_, custom assertions are _first-class citizens_. You can create your own assertions with minimal boilerplate. You don't have to learn a new API or a new DSL (maybe); you just use [Zod][]. _It's so easy, even a **archaic human** could do it!_
90
+
91
+ Read [Guide: How to Create a Custom Assertion](https://boneskull.github.io/bupkis/documents/Guides.How_to_Create_a_Custom_Assertion) to learn more.
92
+
93
+ ## Prerequisites
94
+
95
+ _BUPKIS_ requires **Node.js ^20.19.0 || ^22.12.0 || >=23** and ships as a dual CJS/ESM package.
96
+
97
+ The library has been designed for Node.js environments and testing frameworks.
98
+
99
+ ## Installation
100
+
101
+ ```bash
102
+ npm install bupkis -D
103
+ ```
104
+
105
+ ## Usage
106
+
107
+ Here:
108
+
109
+ ```ts
110
+ import { expect } from 'bupkis';
111
+
112
+ // Basic type assertions
113
+ expect('hello', 'to be a string');
114
+ expect(42, 'to be a number');
115
+ expect(true, 'to be a boolean');
116
+
117
+ // Value comparisons
118
+ expect(10, 'to equal', 10);
119
+ expect('hello world', 'to contain', 'world');
120
+ expect([1, 2, 3], 'to have length', 3);
121
+
122
+ // Negation
123
+ expect(42, 'not to be a string');
124
+ expect('hello', 'not to equal', 'goodbye');
125
+
126
+ // Object assertions
127
+ const user = { name: 'Alice', age: 30 };
128
+ expect(user, 'to be an object');
129
+ expect(user, 'to have property', 'name');
130
+ expect(user, 'to satisfy', { name: 'Alice' });
131
+ ```
132
+
133
+ For comprehensive documentation and guides, visit the [project documentation](https://boneskull.github.io/bupkis/).
134
+
135
+ ### Worth Mentioning Right Now
136
+
137
+ _BUPKIS_ has two main exports:
138
+
139
+ - `expect()`: the main entrypoint for synchronous assertions
140
+ - `expectAsync()`: the main entrypoint for asynchronous assertions
141
+
142
+ > [!IMPORTANT]
143
+ >
144
+ > As of this writing, the assertions available in `expectAsync()` are all `Promise`-related (and custom assertions can even use an async schema for the subject); they are completely disjoint from the assertions available in `expect()`. **This will likely change in the future.**
145
+
146
+ ## Project Scope
147
+
148
+ 1. It's an assertion library
149
+
150
+ ### TODO
151
+
152
+ ## Prior Art & Appreciation
153
+
154
+ - [Unexpected][] is the main inspiration for _BUPKIS_. However, creating types for this library is exceedingly difficult (and was in fact the first thing I tried). Despite that drawback, I find it more usable than any other assertion library I've tried.
155
+ - [Zod][] is a popular object validation library which does most of the heavy lifting for _BUPKIS_. It's not an assertion library, but there's enough overlap in its use case that it makes sense to leverage it.
156
+ - [fast-check][]: A big thanks to Nicholas Dubien for this library. There is **no better library** for an assertion library to use to test itself! Well, besides itself, I mean. How about _in addition to_ itself? Yes. Thank you!
157
+
158
+ ## A Note From The Author
159
+
160
+ > _"This is my assertion library. Many are like it, but this one is mine."_
161
+ >
162
+ > ‒boneskull, 2025
163
+
164
+ ## License
165
+
166
+ Copyright © 2025 Christopher Hiller. Licensed under [BlueOak-1.0.0](https://blueoakcouncil.org/license/1.0.0).
167
+
168
+ [zod]: https://zod.dev
169
+ [unexpected]: https://unexpected.js.org
170
+ [fast-check]: https://fast-check.dev
package/package.json ADDED
@@ -0,0 +1,164 @@
1
+ {
2
+ "name": "bupkis",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "description": "",
6
+ "repository": {
7
+ "url": "git+https://github.com/boneskull/bupkis.git"
8
+ },
9
+ "author": {
10
+ "name": "Christopher Hiller",
11
+ "email": "boneskull@boneskull.com"
12
+ },
13
+ "license": "BlueOak-1.0.0",
14
+ "engines": {
15
+ "node": "^20.19.0 || ^22.12.0 || >=23"
16
+ },
17
+ "main": "./dist/commonjs/index.js",
18
+ "types": "./dist/commonjs/index.d.ts",
19
+ "module": "./dist/esm/index.js",
20
+ "exports": {
21
+ "./package.json": "./package.json",
22
+ ".": {
23
+ "import": {
24
+ "types": "./dist/esm/index.d.ts",
25
+ "default": "./dist/esm/index.js"
26
+ },
27
+ "require": {
28
+ "types": "./dist/commonjs/index.d.ts",
29
+ "default": "./dist/commonjs/index.js"
30
+ }
31
+ }
32
+ },
33
+ "files": [
34
+ "CHANGELOG.md",
35
+ "dist",
36
+ "src"
37
+ ],
38
+ "keywords": [
39
+ "test",
40
+ "expect",
41
+ "assert",
42
+ "assertion",
43
+ "expectation",
44
+ "assertion library",
45
+ "assertions",
46
+ "zod",
47
+ "validation",
48
+ "testing",
49
+ "jest",
50
+ "mocha",
51
+ "chai",
52
+ "vitest",
53
+ "ava",
54
+ "tap",
55
+ "tape"
56
+ ],
57
+ "scripts": {
58
+ "build": "tshy",
59
+ "build:docs": "typedoc",
60
+ "build:docs:watch": "npm run --silent build:docs -- --watch",
61
+ "dev": "tshy --watch",
62
+ "dev:docs": "run-p build:docs:watch serve",
63
+ "lint": "eslint .",
64
+ "lint:commit": "commitlint",
65
+ "lint:fix": "eslint . --fix",
66
+ "lint:staged": "lint-staged",
67
+ "lint:types": "tsc -b .config/tsconfig.eslint.json",
68
+ "lint:types:dev": "npm run --silent lint:types -- --watch",
69
+ "prepare": "husky",
70
+ "serve": "serve docs",
71
+ "test": "node --import tsx --test \"test/**/*.ts\"",
72
+ "test:property": "node --import tsx --test \"test/property/**/*.test.ts\"",
73
+ "test:property:dev": "node --import tsx --test --watch \"test/property/**/*.test.ts\"",
74
+ "test:watch": "node --import tsx --test \"test/**/*.ts\" --watch"
75
+ },
76
+ "peerDependencies": {
77
+ "zod": "^4.1.5"
78
+ },
79
+ "dependencies": {
80
+ "debug": "^4.4.1",
81
+ "slug": "^11.0.0"
82
+ },
83
+ "optionalDependencies": {
84
+ "zod": "^4.1.5"
85
+ },
86
+ "devDependencies": {
87
+ "@commitlint/cli": "19.8.1",
88
+ "@commitlint/config-conventional": "19.8.1",
89
+ "@eslint/js": "9.33.0",
90
+ "@stylistic/eslint-plugin": "5.2.3",
91
+ "@types/node": "22.7.4",
92
+ "@types/slug": "5.0.9",
93
+ "@types/wallabyjs": "0.0.15",
94
+ "eslint": "9.33.0",
95
+ "eslint-plugin-jsonc": "2.20.1",
96
+ "eslint-plugin-perfectionist": "4.15.0",
97
+ "expect-type": "1.2.2",
98
+ "fast-check": "4.2.0",
99
+ "husky": "9.1.7",
100
+ "knip": "5.62.0",
101
+ "lint-staged": "16.1.5",
102
+ "npm-run-all2": "8.0.4",
103
+ "prettier": "3.6.2",
104
+ "prettier-plugin-jsdoc": "1.3.3",
105
+ "prettier-plugin-pkg": "0.21.2",
106
+ "prettier-plugin-sort-json": "4.1.1",
107
+ "serve": "14.2.5",
108
+ "tshy": "3.0.2",
109
+ "tsx": "4.20.5",
110
+ "type-fest": "4.41.0",
111
+ "typedoc": "0.28.12",
112
+ "typedoc-github-theme": "0.3.1",
113
+ "typedoc-plugin-mdn-links": "5.0.9",
114
+ "typedoc-plugin-zod": "1.4.2",
115
+ "typescript": "5.9.2",
116
+ "typescript-eslint": "8.40.0"
117
+ },
118
+ "publishConfig": {
119
+ "access": "public",
120
+ "registry": "https://registry.npmjs.org/"
121
+ },
122
+ "knip": {
123
+ "ignoreDependencies": [
124
+ "@types/wallabyjs",
125
+ "serve"
126
+ ],
127
+ "ignore": [
128
+ ".wallaby.js",
129
+ "**/*.cts",
130
+ "**/*.d.ts"
131
+ ],
132
+ "tags": [
133
+ "-knipignore"
134
+ ],
135
+ "node": {
136
+ "entry": [
137
+ "test/**/*.test.ts"
138
+ ]
139
+ }
140
+ },
141
+ "lint-staged": {
142
+ "*.{ts,cts,js,json,md,yml,json5}": [
143
+ "eslint --fix",
144
+ "prettier --write"
145
+ ]
146
+ },
147
+ "prettier": {
148
+ "jsdocCommentLineStrategy": "keep",
149
+ "jsdocPreferCodeFences": true,
150
+ "plugins": [
151
+ "prettier-plugin-jsdoc",
152
+ "prettier-plugin-pkg",
153
+ "prettier-plugin-sort-json"
154
+ ],
155
+ "singleQuote": true,
156
+ "tsdoc": true
157
+ },
158
+ "tshy": {
159
+ "exports": {
160
+ "./package.json": "./package.json",
161
+ ".": "./src/index.ts"
162
+ }
163
+ }
164
+ }
package/src/api.ts ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Contains the main API types
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ import type { TupleToUnion, UnionToIntersection } from 'type-fest';
8
+
9
+ import type {
10
+ AnyAsyncAssertion,
11
+ AnyAsyncAssertions,
12
+ AnySyncAssertion,
13
+ AnySyncAssertions,
14
+ BuiltinAsyncAssertions,
15
+ BuiltinSyncAssertions,
16
+ } from './assertion/assertion-types.js';
17
+ import type {
18
+ createAssertion,
19
+ createAsyncAssertion,
20
+ } from './assertion/create.js';
21
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
+ import type { expect, expectAsync } from './bootstrap.js';
23
+
24
+ import {
25
+ type InferredExpectSlots,
26
+ type MutableOrReadonly,
27
+ type UseFn,
28
+ } from './types.js';
29
+
30
+ /**
31
+ * Base set of properties included in both {@link Expect} and {@link ExpectAsync}.
32
+ */
33
+ export interface BaseExpect {
34
+ /**
35
+ * Creates a new synchronous assertion.
36
+ */
37
+ createAssertion: typeof createAssertion;
38
+ /**
39
+ * Creates a new asynchronous assertion.
40
+ */
41
+ createAsyncAssertion: typeof createAsyncAssertion;
42
+ /**
43
+ * Fails immediately with optional `reason`.
44
+ *
45
+ * @param reason Reason for failure
46
+ * @throws {AssertionError}
47
+ */
48
+ fail(this: void, reason?: string): never;
49
+ }
50
+
51
+ /**
52
+ * The main synchronous assertion function.
53
+ *
54
+ * Contains properties in {@link ExpectSyncProps}.
55
+ *
56
+ * @template T All synchronous assertions available
57
+ * @template U All asynchronous assertions available; for use in
58
+ * {@link ExpectSyncProps.use}
59
+ * @useDeclaredType
60
+ * @see {@link expect}
61
+ */
62
+
63
+ export type Expect<
64
+ T extends AnySyncAssertions = BuiltinSyncAssertions,
65
+ U extends AnyAsyncAssertions = BuiltinAsyncAssertions,
66
+ > = ExpectFunction<T> & ExpectSyncProps<T, U>;
67
+
68
+ /**
69
+ * The main asynchronous assertion function.
70
+ *
71
+ * Contains properties in {@link ExpectSyncProps}.
72
+ *
73
+ * @useDeclaredType
74
+ * @see {@link expectAsync}
75
+ */
76
+
77
+ export type ExpectAsync<
78
+ T extends AnyAsyncAssertions = BuiltinAsyncAssertions,
79
+ U extends AnySyncAssertions = BuiltinSyncAssertions,
80
+ > = ExpectAsyncFunction<T> & ExpectAsyncProps<T, U>;
81
+
82
+ /**
83
+ * All function overloads for `expectAsync()`; part of {@link ExpectAsync}.
84
+ */
85
+
86
+ export type ExpectAsyncFunction<
87
+ T extends AnyAsyncAssertions = BuiltinAsyncAssertions,
88
+ > = UnionToIntersection<
89
+ TupleToUnion<{
90
+ [K in keyof T]: T[K] extends AnyAsyncAssertion
91
+ ? (
92
+ ...args: MutableOrReadonly<InferredExpectSlots<T[K]['parts']>>
93
+ ) => Promise<void>
94
+ : never;
95
+ }>
96
+ >;
97
+
98
+ /**
99
+ * Properties available on `expectAsync()`; part of {@link ExpectAsync}.
100
+ */
101
+
102
+ export interface ExpectAsyncProps<
103
+ T extends AnyAsyncAssertions,
104
+ U extends AnySyncAssertions,
105
+ > extends BaseExpect {
106
+ /**
107
+ * Tuple of all assertions available in this `expect()`.
108
+ */
109
+ assertions: T;
110
+ /**
111
+ * Function to add more assertions to this `expect()`, returning a new
112
+ * `expect()` and `expectAsync()` pair with the combined assertions.
113
+ */
114
+ use: UseFn<U, T>;
115
+ }
116
+
117
+ /**
118
+ * All function overloads for `expect()`; part of {@link Expect}.
119
+ *
120
+ * @useDeclaredType
121
+ */
122
+ export type ExpectFunction<
123
+ T extends AnySyncAssertions = BuiltinSyncAssertions,
124
+ > = UnionToIntersection<
125
+ TupleToUnion<{
126
+ [K in keyof T]: T[K] extends AnySyncAssertion
127
+ ? (...args: MutableOrReadonly<InferredExpectSlots<T[K]['parts']>>) => void
128
+ : never;
129
+ }>
130
+ >;
131
+
132
+ /**
133
+ * Properties for `expect()`; part of {@link Expect}.
134
+ */
135
+ export interface ExpectSyncProps<
136
+ T extends AnySyncAssertions,
137
+ U extends AnyAsyncAssertions,
138
+ > extends BaseExpect {
139
+ /**
140
+ * Tuple of all assertions available in this `expect()`.
141
+ */
142
+ assertions: T;
143
+
144
+ /**
145
+ * Function to add more assertions to this `expect()`, returning a new
146
+ * `expect()` and `expectAsync()` pair with the combined assertions.
147
+ */
148
+ use: UseFn<T, U>;
149
+ }