vimonkey 0.0.1 → 0.2.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Beorn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # vimonkey
2
+
3
+ Fuzz testing and chaos streams for [Vitest](https://vitest.dev/).
4
+
5
+ ## Fuzz Testing
6
+
7
+ Async generators with auto-shrinking, regression cases, and seeded RNG.
8
+
9
+ ```typescript
10
+ import { test, gen, take } from "vimonkey"
11
+
12
+ test.fuzz("cursor stays in bounds", async () => {
13
+ for await (const key of take(gen(["j", "k", "h", "l"]), 100)) {
14
+ await handle.press(key)
15
+ expect(getCursor()).toBeGreaterThanOrEqual(0)
16
+ }
17
+ // On failure: auto-shrinks to minimal repro, saves to __fuzz_cases__/
18
+ })
19
+ ```
20
+
21
+ Generators support uniform arrays, weighted tuples, sync/async picker functions, and multi-value returns.
22
+
23
+ ## Chaos Streams
24
+
25
+ Composable async iterable transformers that simulate unreliable delivery.
26
+
27
+ ```typescript
28
+ import { chaos, drop, reorder } from "vimonkey/chaos"
29
+
30
+ const chaotic = chaos(
31
+ source,
32
+ [
33
+ { type: "drop", params: { rate: 0.2 } },
34
+ { type: "reorder", params: { windowSize: 5 } },
35
+ { type: "duplicate", params: { rate: 0.1 } },
36
+ ],
37
+ rng,
38
+ )
39
+ ```
40
+
41
+ Built-in transformers: `drop`, `reorder`, `duplicate`, `burst`, `initGap`, `delay`. Extend with custom registries.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ npm install vimonkey
47
+ ```
48
+
49
+ ## Exports
50
+
51
+ ```typescript
52
+ import { test, gen, take } from "vimonkey" // Fuzz testing
53
+ import { chaos, drop, reorder } from "vimonkey/chaos" // Chaos streams
54
+ import { viMonkeyPlugin } from "vimonkey/plugin" // Vitest plugin
55
+ ```
56
+
57
+ ## License
58
+
59
+ MIT
package/package.json CHANGED
@@ -1,6 +1,65 @@
1
1
  {
2
2
  "name": "vimonkey",
3
- "version": "0.0.1",
4
- "description": "Reserved",
5
- "license": "MIT"
6
- }
3
+ "version": "0.2.1",
4
+ "description": "Fuzz testing with auto-shrinking and composable chaos stream transformers for Vitest",
5
+ "keywords": [
6
+ "async-generator",
7
+ "auto-shrink",
8
+ "chaos",
9
+ "chaos-engineering",
10
+ "delta-debugging",
11
+ "fuzz",
12
+ "fuzzing",
13
+ "property-based-testing",
14
+ "seeded-random",
15
+ "shrinking",
16
+ "stream-transformer",
17
+ "testing",
18
+ "vitest",
19
+ "vitest-plugin"
20
+ ],
21
+ "homepage": "https://github.com/beorn/vimonkey",
22
+ "bugs": {
23
+ "url": "https://github.com/beorn/vimonkey/issues"
24
+ },
25
+ "license": "MIT",
26
+ "author": "Beorn",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/beorn/vimonkey.git"
30
+ },
31
+ "files": [
32
+ "src"
33
+ ],
34
+ "type": "module",
35
+ "module": "./src/index.ts",
36
+ "exports": {
37
+ ".": "./src/index.ts",
38
+ "./plugin": "./src/plugin.ts",
39
+ "./fuzz": "./src/fuzz/index.ts",
40
+ "./chaos": "./src/chaos/index.ts"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "test": "vitest",
48
+ "test:run": "vitest run",
49
+ "prepublishOnly": "bun run build"
50
+ },
51
+ "dependencies": {
52
+ "debug": "^4.4.0"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^22.0.0",
56
+ "typescript": "^5.9.3",
57
+ "vitest": "^4.0.0"
58
+ },
59
+ "peerDependencies": {
60
+ "vitest": ">=4.0.0"
61
+ },
62
+ "engines": {
63
+ "node": ">=23.6.0"
64
+ }
65
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Chaos stream transformers for fuzz testing
3
+ *
4
+ * Composable async iterable transformers that sit between gen() and take()
5
+ * to simulate unreliable delivery: dropped messages, reordering, duplicates,
6
+ * bursts, delays, and gaps.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { gen, take } from "vimonkey"
11
+ * import { drop, reorder, chaos } from "vimonkey/chaos"
12
+ *
13
+ * const base = gen(picker)
14
+ * const chaotic = reorder(drop(base, 0.2, rng), 5, rng)
15
+ * for await (const event of take(chaotic, 100)) { ... }
16
+ * ```
17
+ */
18
+
19
+ import type { SeededRandom } from "../random.js"
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Individual transformers
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Skip items with probability `rate`.
27
+ * Simulates message loss, queue overflow, network drops.
28
+ */
29
+ export async function* drop<T>(source: AsyncIterable<T>, rate: number, rng: SeededRandom): AsyncGenerator<T> {
30
+ for await (const item of source) {
31
+ if (!rng.bool(rate)) yield item
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Buffer up to `windowSize` items, shuffle, yield when buffer is full.
37
+ * Simulates out-of-order delivery, non-deterministic event ordering.
38
+ */
39
+ export async function* reorder<T>(source: AsyncIterable<T>, windowSize: number, rng: SeededRandom): AsyncGenerator<T> {
40
+ const buffer: T[] = []
41
+ for await (const item of source) {
42
+ buffer.push(item)
43
+ if (buffer.length >= windowSize) {
44
+ const shuffled = rng.shuffle(buffer)
45
+ buffer.length = 0
46
+ for (const e of shuffled) yield e
47
+ }
48
+ }
49
+ if (buffer.length > 0) {
50
+ const shuffled = rng.shuffle(buffer)
51
+ for (const e of shuffled) yield e
52
+ }
53
+ }
54
+
55
+ /**
56
+ * With probability `rate`, yield the item twice.
57
+ * Simulates duplicate delivery, at-least-once semantics.
58
+ */
59
+ export async function* duplicate<T>(source: AsyncIterable<T>, rate: number, rng: SeededRandom): AsyncGenerator<T> {
60
+ for await (const item of source) {
61
+ yield item
62
+ if (rng.bool(rate)) yield item
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Collect `burstSize` items, then yield them all at once.
68
+ * Simulates bursty delivery, batched network packets.
69
+ */
70
+ export async function* burst<T>(source: AsyncIterable<T>, burstSize: number): AsyncGenerator<T> {
71
+ const buffer: T[] = []
72
+ for await (const item of source) {
73
+ buffer.push(item)
74
+ if (buffer.length >= burstSize) {
75
+ for (const e of buffer) yield e
76
+ buffer.length = 0
77
+ }
78
+ }
79
+ for (const e of buffer) yield e
80
+ }
81
+
82
+ /**
83
+ * Skip the first `count` items.
84
+ * Simulates missed events during initialization, late subscriber.
85
+ */
86
+ export async function* initGap<T>(source: AsyncIterable<T>, count: number): AsyncGenerator<T> {
87
+ let skipped = 0
88
+ for await (const item of source) {
89
+ if (skipped < count) {
90
+ skipped++
91
+ continue
92
+ }
93
+ yield item
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Add a random delay before yielding each item.
99
+ * Simulates slow I/O, network latency, disk delays.
100
+ */
101
+ export async function* delay<T>(
102
+ source: AsyncIterable<T>,
103
+ minMs: number,
104
+ maxMs: number,
105
+ rng: SeededRandom,
106
+ ): AsyncGenerator<T> {
107
+ for await (const item of source) {
108
+ const ms = rng.int(minMs, maxMs)
109
+ await new Promise((resolve) => setTimeout(resolve, ms))
110
+ yield item
111
+ }
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Combinator
116
+ // ---------------------------------------------------------------------------
117
+
118
+ /** Configuration for a chaos transformer in the pipeline */
119
+ export interface ChaosConfig {
120
+ type: string
121
+ params: Record<string, unknown>
122
+ }
123
+
124
+ /** Registry of transformer factories keyed by config type */
125
+ export type ChaosRegistry<T> = Record<
126
+ string,
127
+ (source: AsyncIterable<T>, params: Record<string, unknown>, rng: SeededRandom) => AsyncIterable<T>
128
+ >
129
+
130
+ /** Built-in transformer registry (works for any T) */
131
+ const BUILTIN_REGISTRY: ChaosRegistry<unknown> = {
132
+ drop: (s, p, rng) => drop(s, (p.rate as number) ?? 0.2, rng),
133
+ reorder: (s, p, rng) => reorder(s, (p.windowSize as number) ?? 5, rng),
134
+ duplicate: (s, p, rng) => duplicate(s, (p.rate as number) ?? 0.3, rng),
135
+ burst: (s, p) => burst(s, (p.burstSize as number) ?? 10),
136
+ initGap: (s, p) => initGap(s, (p.count as number) ?? 5),
137
+ delay: (s, p, rng) => delay(s, (p.minMs as number) ?? 1, (p.maxMs as number) ?? 5, rng),
138
+ }
139
+
140
+ /**
141
+ * Compose multiple chaos transformer configs into a single async iterable pipeline.
142
+ *
143
+ * Uses built-in transformers by default. Pass a custom `registry` to add
144
+ * domain-specific transformers (e.g., FS-specific atomicSave, coalesce).
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * const chaotic = chaos(source, [
149
+ * { type: "drop", params: { rate: 0.2 } },
150
+ * { type: "reorder", params: { windowSize: 5 } },
151
+ * ], rng)
152
+ * ```
153
+ *
154
+ * @example Custom registry
155
+ * ```typescript
156
+ * const chaotic = chaos(source, configs, rng, {
157
+ * ...builtinChaosRegistry,
158
+ * atomic_save: (s, p, rng) => atomicSave(s, p.rate, rng),
159
+ * })
160
+ * ```
161
+ */
162
+ export function chaos<T>(
163
+ source: AsyncIterable<T>,
164
+ configs: ChaosConfig[],
165
+ rng: SeededRandom,
166
+ registry?: ChaosRegistry<T>,
167
+ ): AsyncIterable<T> {
168
+ const reg = registry ?? (BUILTIN_REGISTRY as ChaosRegistry<T>)
169
+ let pipeline = source
170
+ for (const config of configs) {
171
+ const factory = reg[config.type]
172
+ if (factory) {
173
+ pipeline = factory(pipeline, config.params, rng)
174
+ }
175
+ }
176
+ return pipeline
177
+ }
178
+
179
+ /** Re-export the built-in registry for extension */
180
+ export const builtinChaosRegistry: ChaosRegistry<unknown> = BUILTIN_REGISTRY
package/src/env.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * TEST_SYS environment handling
3
+ *
4
+ * Controls the implementation used during tests:
5
+ * - fake: Use fake/mock implementations (fast, isolated)
6
+ * - real:mem: Real implementation with in-memory storage
7
+ * - real:disk: Real implementation with temporary disk storage
8
+ */
9
+
10
+ export type TestSys = "fake" | "real:mem" | "real:disk"
11
+
12
+ const VALID_VALUES: TestSys[] = ["fake", "real:mem", "real:disk"]
13
+
14
+ /**
15
+ * Get the current TEST_SYS value
16
+ *
17
+ * @returns The test system type, defaults to 'fake'
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { getTestSys } from 'vimonkey'
22
+ *
23
+ * const sys = getTestSys()
24
+ * if (sys === 'fake') {
25
+ * // use mock
26
+ * } else if (sys === 'real:mem') {
27
+ * // use real with in-memory storage
28
+ * } else {
29
+ * // use real with disk storage
30
+ * }
31
+ * ```
32
+ */
33
+ export function getTestSys(): TestSys {
34
+ const value = process.env.TEST_SYS
35
+
36
+ if (!value) {
37
+ return "fake"
38
+ }
39
+
40
+ if (!VALID_VALUES.includes(value as TestSys)) {
41
+ console.warn(
42
+ `Invalid TEST_SYS value: "${value}". ` + `Valid values: ${VALID_VALUES.join(", ")}. ` + `Defaulting to "fake".`,
43
+ )
44
+ return "fake"
45
+ }
46
+
47
+ return value as TestSys
48
+ }
49
+
50
+ /**
51
+ * Check if running with real implementation
52
+ */
53
+ export function isRealSys(): boolean {
54
+ const sys = getTestSys()
55
+ return sys === "real:mem" || sys === "real:disk"
56
+ }
57
+
58
+ /**
59
+ * Check if running with disk storage
60
+ */
61
+ export function isDiskSys(): boolean {
62
+ return getTestSys() === "real:disk"
63
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Fuzz test context using AsyncLocalStorage for auto-tracking
3
+ *
4
+ * When running inside test.fuzz(), take() automatically records
5
+ * yielded values for shrinking and regression testing.
6
+ */
7
+ import { AsyncLocalStorage } from "node:async_hooks"
8
+
9
+ export interface FuzzContext {
10
+ /** Recorded history of yielded values */
11
+ history: unknown[]
12
+ /** If replaying, the sequence to replay */
13
+ replaySequence: unknown[] | null
14
+ /** Current index in replay sequence */
15
+ replayIndex: number
16
+ /** The seed used for this test run */
17
+ seed: number
18
+ }
19
+
20
+ /** AsyncLocalStorage for fuzz test context */
21
+ export const fuzzContext = new AsyncLocalStorage<FuzzContext>()
22
+
23
+ /**
24
+ * Create a new fuzz context
25
+ */
26
+ export function createFuzzContext(seed: number): FuzzContext {
27
+ return {
28
+ history: [],
29
+ replaySequence: null,
30
+ replayIndex: 0,
31
+ seed,
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Create a replay context from a saved sequence
37
+ */
38
+ export function createReplayContext(sequence: unknown[], seed: number): FuzzContext {
39
+ return {
40
+ history: [],
41
+ replaySequence: sequence,
42
+ replayIndex: 0,
43
+ seed,
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Check if we're currently in a fuzz context
49
+ */
50
+ export function isInFuzzContext(): boolean {
51
+ return fuzzContext.getStore() !== undefined
52
+ }
53
+
54
+ /**
55
+ * Get the current fuzz context or undefined
56
+ */
57
+ export function getFuzzContext(): FuzzContext | undefined {
58
+ return fuzzContext.getStore()
59
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Ergonomic generator API for fuzz testing
3
+ *
4
+ * gen(picker) creates an infinite async generator from a picker.
5
+ * take(generator, n) limits iterations and auto-tracks in test.fuzz().
6
+ */
7
+
8
+ import { createSeededRandom, weightedPickFromTuples, type SeededRandom } from "../random.js"
9
+ import { fuzzContext } from "./context.js"
10
+
11
+ /**
12
+ * Picker context passed to picker functions
13
+ */
14
+ export interface PickerContext {
15
+ /** Seeded random number generator */
16
+ random: SeededRandom
17
+ /** Current iteration (0-indexed) */
18
+ iteration: number
19
+ }
20
+
21
+ /**
22
+ * Result type for picker functions - can return single value, array, or iterable
23
+ */
24
+ type PickerResult<T> = T | T[] | Iterable<T>
25
+
26
+ /**
27
+ * Sync picker function
28
+ */
29
+ type SyncPickerFn<T> = (ctx: PickerContext) => PickerResult<T>
30
+
31
+ /**
32
+ * Async picker function (for AI mode)
33
+ */
34
+ type AsyncPickerFn<T> = (ctx: PickerContext) => Promise<PickerResult<T>>
35
+
36
+ /**
37
+ * Picker types:
38
+ * - T[] - random from array
39
+ * - [number, T][] - weighted random (pairs of [weight, value])
40
+ * - (ctx) => T | T[] | Iterable<T> - sync function
41
+ * - (ctx) => Promise<T | T[] | Iterable<T>> - async function
42
+ */
43
+ export type Picker<T> = T[] | [number, T][] | SyncPickerFn<T> | AsyncPickerFn<T>
44
+
45
+ /**
46
+ * Type guard for weighted tuples
47
+ */
48
+ function isWeightedTuple<T>(picker: unknown): picker is [number, T][] {
49
+ return Array.isArray(picker) && picker.length > 0 && Array.isArray(picker[0]) && typeof picker[0][0] === "number"
50
+ }
51
+
52
+ /**
53
+ * Type guard for iterables (excluding strings)
54
+ */
55
+ function isIterable<T>(value: unknown): value is Iterable<T> {
56
+ return value !== null && typeof value === "object" && Symbol.iterator in value && typeof value !== "string"
57
+ }
58
+
59
+ /**
60
+ * Flatten picker result: single value, array, or iterable → individual items
61
+ */
62
+ function* flatten<T>(result: PickerResult<T>): Generator<T> {
63
+ if (Array.isArray(result)) {
64
+ for (const item of result) yield item
65
+ } else if (isIterable(result)) {
66
+ for (const item of result) yield item
67
+ } else {
68
+ yield result
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Create a picker function from various picker specs
74
+ */
75
+ function createPicker<T>(
76
+ picker: Picker<T>,
77
+ random: SeededRandom,
78
+ ): (ctx: PickerContext) => PickerResult<T> | Promise<PickerResult<T>> {
79
+ // Function picker - use as-is
80
+ if (typeof picker === "function") {
81
+ return picker
82
+ }
83
+
84
+ // Weighted tuple picker
85
+ if (isWeightedTuple<T>(picker)) {
86
+ const items = picker
87
+ return () => weightedPickFromTuples(items, random.float())
88
+ }
89
+
90
+ // Array picker - random from array
91
+ return () => picker[Math.floor(random.float() * picker.length)]!
92
+ }
93
+
94
+ /**
95
+ * Create an infinite async generator from a picker
96
+ *
97
+ * @example
98
+ * // Random from array
99
+ * gen(['j', 'k', 'h', 'l'])
100
+ *
101
+ * @example
102
+ * // Weighted random
103
+ * gen([[40, 'j'], [40, 'k'], [20, 'Enter']])
104
+ *
105
+ * @example
106
+ * // Custom picker function
107
+ * gen(({ random }) => random.pick(['j', 'k']))
108
+ *
109
+ * @example
110
+ * // Picker returns array (flattened)
111
+ * gen(() => ['j', 'j', 'Enter']) // yields: j, j, Enter, j, j, Enter, ...
112
+ */
113
+ export async function* gen<T>(picker: Picker<T>, seed?: number): AsyncGenerator<T> {
114
+ // Use context seed if available, otherwise provided seed or Date.now()
115
+ const ctx = fuzzContext.getStore()
116
+ const random = createSeededRandom(seed ?? ctx?.seed ?? Date.now())
117
+ const pick = createPicker(picker, random)
118
+ let iteration = 0
119
+
120
+ while (true) {
121
+ const pickerCtx: PickerContext = { random, iteration: iteration++ }
122
+ const result = await pick(pickerCtx)
123
+ yield* flatten(result)
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Limit an async generator to n iterations
129
+ *
130
+ * When running inside test.fuzz(), automatically tracks yielded values
131
+ * for shrinking and regression testing.
132
+ *
133
+ * @example
134
+ * for await (const key of take(gen(['j', 'k']), 100)) {
135
+ * await handle.press(key)
136
+ * }
137
+ */
138
+ export async function* take<T>(generator: AsyncIterable<T>, n: number): AsyncGenerator<T> {
139
+ const ctx = fuzzContext.getStore()
140
+ let i = 0
141
+
142
+ // Replay mode: yield from saved sequence instead of generator
143
+ if (ctx?.replaySequence) {
144
+ while (i < n && ctx.replayIndex < ctx.replaySequence.length) {
145
+ yield ctx.replaySequence[ctx.replayIndex++] as T
146
+ i++
147
+ }
148
+ return
149
+ }
150
+
151
+ // Normal mode: yield from generator, optionally record
152
+ for await (const item of generator) {
153
+ if (i++ >= n) break
154
+ ctx?.history.push(item) // Track if in fuzz context
155
+ yield item
156
+ }
157
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Fuzz testing API
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { test, gen, take, createSeededRandom } from 'vimonkey/fuzz'
7
+ *
8
+ * // Simple random from array
9
+ * test('navigation', async () => {
10
+ * const handle = await run(<Board />, { cols: 80, rows: 24 })
11
+ * for await (const key of take(gen(['j','k','h','l']), 100)) {
12
+ * await handle.press(key)
13
+ * expect(handle.locator('[data-cursor]').count()).toBe(1)
14
+ * }
15
+ * })
16
+ *
17
+ * // Weighted random
18
+ * test('weighted', async () => {
19
+ * for await (const key of take(gen([[40,'j'], [40,'k'], [20,'Enter']]), 100)) {
20
+ * await handle.press(key)
21
+ * }
22
+ * })
23
+ *
24
+ * // Stateful with closure
25
+ * test('stateful', async () => {
26
+ * const handle = await app.run(<Board />)
27
+ * const random = createSeededRandom(Date.now())
28
+ *
29
+ * const keys = async function*() {
30
+ * while (true) {
31
+ * const state = handle.store.getState()
32
+ * yield state.cursor === 0 ? random.pick(['j','l']) : random.pick(['j','k','h','l'])
33
+ * }
34
+ * }
35
+ *
36
+ * for await (const key of take(keys(), 100)) {
37
+ * await handle.press(key)
38
+ * }
39
+ * })
40
+ *
41
+ * // With auto-tracking and shrinking
42
+ * test.fuzz('cursor invariants', async () => {
43
+ * for await (const key of take(gen(['j','k']), 100)) {
44
+ * await handle.press(key)
45
+ * expect(...) // On failure: shrinks, saves to __fuzz_cases__/
46
+ * }
47
+ * })
48
+ * ```
49
+ */
50
+
51
+ // Core API
52
+ export { gen, take, type Picker, type PickerContext } from "./gen.js"
53
+
54
+ // test.fuzz wrapper
55
+ export { test, FuzzError, type FuzzTestOptions } from "./test-fuzz.js"
56
+ export { describe, expect, it, beforeAll, afterAll, beforeEach, afterEach } from "./test-fuzz.js"
57
+
58
+ // Context (for advanced use)
59
+ export {
60
+ fuzzContext,
61
+ getFuzzContext,
62
+ isInFuzzContext,
63
+ createFuzzContext,
64
+ createReplayContext,
65
+ type FuzzContext,
66
+ } from "./context.js"
67
+
68
+ // Shrinking (for advanced use)
69
+ export { shrinkSequence, formatShrinkResult, type ShrinkOptions, type ShrinkResult } from "./shrink.js"
70
+
71
+ // Regression (for advanced use)
72
+ export {
73
+ saveCase,
74
+ loadCases,
75
+ loadCasesForTest,
76
+ deleteCase,
77
+ clearCases,
78
+ getFuzzCasesDir,
79
+ type SavedCase,
80
+ } from "./regression.js"
81
+
82
+ // Re-export random utilities
83
+ export {
84
+ createSeededRandom,
85
+ weightedPickFromTuples,
86
+ parseSeed,
87
+ parseRepeats,
88
+ deriveSeeds,
89
+ type SeededRandom,
90
+ } from "../random.js"