@carlwr/fastcheck-utils 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Carl Wernhoff
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,144 @@
1
+
2
+ # fastcheck-utils
3
+
4
+ _improved generators for [fast-check](https://github.com/dubzzz/fast-check)_
5
+
6
+ Links:
7
+ * github: [github.com/carlwr/fastcheck-utils](https://github.com/carlwr/fastcheck-utils)
8
+ * npm: [www.npmjs.com/package/fastcheck-utils](https://www.npmjs.com/package/fastcheck-utils)
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @carlwr/fastcheck-utils
14
+ ```
15
+
16
+ `fast-check` is a peer dependency of this package. It should be picked up by the package manager also if you depend on e.g. `@fast-check/vitest` or `@fast-check/jest` rather than on `fast-check` directly.
17
+
18
+ to run checks and tests:
19
+ ```bash
20
+ npm qa
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Generators
26
+
27
+
28
+ ### `element`
29
+
30
+ ```ts
31
+ function element<T>(xs: readonly [T, T]): Arbitrary<T>
32
+ ```
33
+ Randomly choose one of the constant array values.
34
+
35
+ Shrinking is done towards the first element.
36
+
37
+ Similar to fc.constantFrom, but _shrinks across all elements_ - fc.constantFrom only shrinks towards the first element.
38
+
39
+ example:
40
+
41
+ ```ts
42
+ import * as fcu from 'fastcheck-utils'
43
+ import * as fc from 'fast-check'
44
+
45
+ const arb = fcu.element(['a', 'b', 'c'] as const)
46
+ const samples = fc.sample(arb, {seed:1, numRuns:3})
47
+ console.log(samples) // ['b', 'c', 'b']
48
+ ```
49
+
50
+ ### `getNext`
51
+
52
+ ```ts
53
+ function getNext<T>(stream: InfiniteStream<T>): T
54
+ ```
55
+ Get next value from an `InfiniteStream` object yielded by `infiniteStream`.
56
+
57
+ Throws if the stream is unexpectedly done (it is my understanding that this should never happen).
58
+
59
+ ### `infiniteStream`
60
+
61
+ ```ts
62
+ function infiniteStream<T>(arb: Arbitrary<T>): Arbitrary<InfiniteStream<T>>
63
+ ```
64
+ Generate an infinite stream of values.
65
+
66
+ This arbitrary is a minimal wrapper around fc.infiniteStream allowing access to the generated values in a type-safe way through the `getNext` helper.
67
+
68
+ features and non-features:
69
+ - does _not_ shrink at all unfortunately - since fc.infiniteStream doesn't
70
+ - _does_ print a meaningful counterexample and execution summary on failure, that includes some of the previously tried values in the stream
71
+
72
+ example:
73
+
74
+ ```ts
75
+ import * as fcu from 'fastcheck-utils'
76
+ import * as fc from 'fast-check'
77
+
78
+ const arb = fcu.infiniteStream(fc.nat({max:10}))
79
+ const stream = fc.sample(arb, {seed:1})[0] ?? fail()
80
+ console.log(fcu.getNext(stream)) // 8
81
+ console.log(fcu.getNext(stream)) // 2
82
+ ```
83
+
84
+ ### `nonEmptyArray`
85
+
86
+ ```ts
87
+ function nonEmptyArray<T>(arb: Arbitrary<T>, constraints?: ArrayConstraints): Arbitrary<[T, ...T[]]>
88
+ ```
89
+ Generate a non-empty array.
90
+
91
+ If a `constraints` parameter object is passed, it will be honored (function throws if `{minLength: 0}` is specified).
92
+
93
+ example:
94
+
95
+ ```ts
96
+ import * as fcu from 'fastcheck-utils'
97
+ import * as fc from 'fast-check'
98
+
99
+ const arb = fcu.nonEmptyArray(fc.nat({max:5}))
100
+ const sample = fc.sample(arb, {seed:1})[0]
101
+ console.log(sample) // [5, 4, 2, 2, 5, 0, 3, 1, 5, 3, 1]
102
+ ```
103
+
104
+ ### `nonEmptyUniqueArray`
105
+
106
+ ```ts
107
+ function nonEmptyUniqueArray<T, U>(arb: Arbitrary<T>, constraints?: UniqueArrayConstraints<T, U>): Arbitrary<[T, ...T[]]>
108
+ ```
109
+ Generate a non-empty array of unique values.
110
+
111
+ example:
112
+
113
+ ```ts
114
+ import * as fcu from 'fastcheck-utils'
115
+ import * as fc from 'fast-check'
116
+
117
+ const arb = fcu.nonEmptyUniqueArray(fc.nat({max:10}))
118
+ const sample = fc.sample(arb, {seed:1})[0]
119
+ console.log(sample) // [2, 6, 5, 9, 4, 7, 10, 3, 1, 0, 8]
120
+ ```
121
+
122
+ ### `record`
123
+
124
+ ```ts
125
+ function record<T>(model: Model<T>): Arbitrary<ExactRecord<T>>
126
+
127
+ function record<T>(model: Model<T>, constr: AllKeysRequired<T>): Arbitrary<ExactRecord<T>>
128
+
129
+ function record<T, K>(model: Model<T>, constr: SomeKeysRequired<T, K>): Arbitrary<{ [K in string | number | symbol]: (Partial<T> & Pick<T, K & keyof T>)[K] }>
130
+ ```
131
+ like fc.record, but with
132
+ - `noNullPrototype` _true_ by default
133
+ - stronger typing
134
+
135
+ example:
136
+
137
+ ```ts
138
+ import * as fcu from 'fastcheck-utils'
139
+ import * as fc from 'fast-check'
140
+
141
+ const arb = fcu.record({name: fc.string(), age: fc.nat({max: 100})})
142
+ const sample = fc.sample(arb, {seed:1})[0] ?? fail()
143
+ console.log(sample) // {name: 'TVb~o"nP', age: 36}
144
+ ```
@@ -0,0 +1,105 @@
1
+ import * as fc from 'fast-check';
2
+
3
+ /**
4
+ * Randomly choose one of the constant array values.
5
+ *
6
+ * Shrinking is done towards the first element.
7
+ *
8
+ * Similar to {@link fc.constantFrom}, but _shrinks across all elements_ - {@link fc.constantFrom} only shrinks towards the first element.
9
+ *
10
+ * @example
11
+ * import * as fcu from 'fastcheck-utils'
12
+ * import * as fc from 'fast-check'
13
+ *
14
+ * const arb = fcu.element(['a', 'b', 'c'] as const)
15
+ * const samples = fc.sample(arb, {seed:1, numRuns:3})
16
+ * console.log(samples) // ['b', 'c', 'b']
17
+ */
18
+ declare function element<T>(xs: readonly [T, ...T[]]): fc.Arbitrary<T>;
19
+
20
+ interface InfiniteStream<T> extends fc.Stream<T> {
21
+ }
22
+ /**
23
+ * Generate an infinite stream of values.
24
+ *
25
+ * This arbitrary is a minimal wrapper around {@link fc.infiniteStream} allowing access to the generated values in a type-safe way through the {@link getNext} helper.
26
+ *
27
+ * features and non-features:
28
+ * - does _not_ shrink at all unfortunately - since {@link fc.infiniteStream} doesn't
29
+ * - _does_ print a meaningful counterexample and execution summary on failure, that includes some of the previously tried values in the stream
30
+ *
31
+ * @example
32
+ * import * as fcu from 'fastcheck-utils'
33
+ * import * as fc from 'fast-check'
34
+ *
35
+ * const arb = fcu.infiniteStream(fc.nat({max:10}))
36
+ * const stream = fc.sample(arb, {seed:1})[0] ?? fail()
37
+ * console.log(fcu.getNext(stream)) // 8
38
+ * console.log(fcu.getNext(stream)) // 2
39
+ */
40
+ declare function infiniteStream<T>(arb: fc.Arbitrary<T>): fc.Arbitrary<InfiniteStream<T>>;
41
+ /**
42
+ * Get next value from an {@link InfiniteStream} object yielded by {@link infiniteStream}.
43
+ *
44
+ * Throws if the stream is unexpectedly done (it is my understanding that this should never happen).
45
+ */
46
+ declare function getNext<T>(stream: InfiniteStream<T>): T;
47
+
48
+ /**
49
+ * Generate a non-empty array.
50
+ *
51
+ * If a {@link constraints} parameter object is passed, it will be honored (function throws if `{minLength: 0}` is specified).
52
+ *
53
+ * @example
54
+ * import * as fcu from 'fastcheck-utils'
55
+ * import * as fc from 'fast-check'
56
+ *
57
+ * const arb = fcu.nonEmptyArray(fc.nat({max:5}))
58
+ * const sample = fc.sample(arb, {seed:1})[0]
59
+ * console.log(sample) // [5, 4, 2, 2, 5, 0, 3, 1, 5, 3, 1]
60
+ */
61
+ declare function nonEmptyArray<T>(arb: fc.Arbitrary<T>, constraints?: fc.ArrayConstraints): fc.Arbitrary<[T, ...T[]]>;
62
+
63
+ /**
64
+ * like {@link fc.record}, but with
65
+ * - `noNullPrototype` _true_ by default
66
+ * - stronger typing
67
+ *
68
+ * @example
69
+ * import * as fcu from 'fastcheck-utils'
70
+ * import * as fc from 'fast-check'
71
+ *
72
+ * const arb = fcu.record({name: fc.string(), age: fc.nat({max: 100})})
73
+ * const sample = fc.sample(arb, {seed:1})[0] ?? fail()
74
+ * console.log(sample) // {name: 'TVb~o"nP', age: 36}
75
+ */
76
+ declare function record<T>(model: Model<T>): fc.Arbitrary<ExactRecord<T>>;
77
+ declare function record<T>(model: Model<T>, constr: AllKeysRequired<T>): fc.Arbitrary<ExactRecord<T>>;
78
+ declare function record<T, K extends keyof T>(model: Model<T>, constr: SomeKeysRequired<T, K>): fc.Arbitrary<fc.RecordValue<T, K>>;
79
+ type Model<T> = {
80
+ [K in keyof T]: fc.Arbitrary<T[K]>;
81
+ };
82
+ type ExactRecord<T> = {
83
+ [K in keyof T]: T[K];
84
+ };
85
+ type AllKeysRequired<T> = fc.RecordConstraints<keyof T> & {
86
+ requiredKeys: readonly (keyof T)[];
87
+ };
88
+ type SomeKeysRequired<T, K extends keyof T> = fc.RecordConstraints<K> & {
89
+ requiredKeys: readonly K[];
90
+ };
91
+
92
+ /**
93
+ * Generate a non-empty array of unique values.
94
+ *
95
+ * @example
96
+ * import * as fcu from 'fastcheck-utils'
97
+ * import * as fc from 'fast-check'
98
+ *
99
+ * const arb = fcu.nonEmptyUniqueArray(fc.nat({max:10}))
100
+ * const sample = fc.sample(arb, {seed:1})[0]
101
+ * console.log(sample) // [2, 6, 5, 9, 4, 7, 10, 3, 1, 0, 8]
102
+ */
103
+ declare function nonEmptyUniqueArray<T, U = T>(arb: fc.Arbitrary<T>, constraints?: fc.UniqueArrayConstraints<T, U>): fc.Arbitrary<[T, ...T[]]>;
104
+
105
+ export { type InfiniteStream, element, getNext, infiniteStream, nonEmptyArray, nonEmptyUniqueArray, record };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ function a(r,t){if(t<0||t>=r.length)throw new Error(`index ${t} out of bounds for array of length ${r.length}`);return r[t]}function c(r){return r.length>=1}import*as f from"fast-check";function h(r){return f.nat({max:r.length-1}).map(e=>a(r,e))}import*as s from"fast-check";function b(r){return s.infiniteStream(r)}function g(r){let{value:t,done:e}=r.next();if(e)throw new Error("infinite stream unexpectedly done");return t}import*as u from"fast-check";function E(r,t){if(t?.minLength===0)throw new Error("minLength cannot be 0 for non-empty array");let e=t??{minLength:1},n=e.minLength!==void 0?e:{...e,minLength:1};return u.array(r,n)}import*as i from"fast-check";function R(r,t){let e=t?.noNullPrototype??!0;if(!t?.requiredKeys)return i.record(r,{noNullPrototype:e});let n={noNullPrototype:e,requiredKeys:t.requiredKeys},o=i.record(r,n),l=Object.keys(r);return m(t.requiredKeys,l),o}function m(r,t){return r.length===t.length&&t.every(r.includes)}import*as y from"fast-check";function k(r,t){let e=t??{},n={...e,minLength:Math.max(e.minLength??1,1)};return y.uniqueArray(r,n).filter(c)}export{h as element,g as getNext,b as infiniteStream,E as nonEmptyArray,k as nonEmptyUniqueArray,R as record};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../typescript-extra/src/misc.ts","../../typescript-extra/src/extract.ts","../src/generators/element.ts","../src/generators/infiniteStream.ts","../src/generators/nonEmptyArray.ts","../src/generators/record.ts","../src/generators/nonEmptyUniqueArray.ts"],"sourcesContent":["import { rm } from \"node:fs/promises\";\n\n/**\n * A non-empty array\n */\nexport type NonEmpty<T> = [T, ...T[]]\n\nexport type Primitive =\n | string\n | number\n | boolean\n | bigint\n | symbol\n | null\n | undefined\n\n\n/**\n * Cached, lazy, single-flight evaluation of a promise.\n *\n * A rejected promise is cached as well, i.e. no re-attempts (failures are assumed to be permanent).\n */\nexport function memoized<T>(f: () => Promise<T>): () => Promise<T> {\n let p: Promise<T> | null = null;\n return () => p ?? (p = f())\n}\n\n\n/**\n * whether the elements of {@link xs} are unique, in the `===` sense\n *\n * O(n)\n * (if the hash tables behave, and they should for primitives)\n * (constants likely not too great)\n*/\nexport function allUnique<T extends Primitive>(xs: T[]): boolean {\n return new Set(xs).size === xs.length\n}\n\n\n/**\n * Map over non-empty array while preserving the type as non-empty\n */\nexport function mapNonEmpty<T,U>(\n xs: readonly [T,...T[]],\n fn: (x: T) => U\n): [U,...U[]] {\n const mapped = xs.map(fn)\n return mapped as [U,...U[]]\n}\n\n/**\n * Map over non-empty array with a function that returns a non-empty array, and return a flattened non-empty array\n */\nexport function flatmapNonEmpty<T,U>(\n xs: readonly [T,...T[]],\n fn: (x: T) => [U,...U[]]\n): [U,...U[]] {\n const mapped = xs.map(fn)\n const flattened = mapped.flat()\n return flattened as [U,...U[]]\n}\n\n\n/**\n * Stateful iterator yielding the elements of {@link xs}.\n *\n * When the last element is reached, calls will continue to return that element indefinitely.\n *\n * @example\n * const next = drain([1, 2, 3]);\n * next(); // => 1\n * next(); // => 2\n * next(); // => 3\n * next(); // => 3\n * next(); // => 3 (returns 3 forever)\n */\nexport function drain<T>(xs: NonEmpty<T>): () => T {\n let state: NonEmpty<T> = xs\n return () => {\n if (hasAtleastTwo(state)) {\n const [x0,...xs_rest] = state\n state = xs_rest\n return x0\n }\n return state[0]\n }\n}\n\n/**\n * return the `i`th element of `xs`, or throw if `i` is out of bounds\n */\nexport function safeIndex<T>(xs: readonly [T, ...T[]], i: number): T {\n if (i<0 || i>=xs.length) {\n throw new Error(`index ${i} out of bounds for array of length ${xs.length}`)\n }\n return xs[i] as T\n}\n\nexport function mapFilterAsync<T, U>(\n xs: readonly T[],\n f: (x: T) => Promise<U|null|undefined>\n): Promise<U[]> {\n return Promise.all(xs.map(f)).then(results => results.filter(isDefined));\n}\n\nexport function mapAsync<T, U>(\n xs: readonly T[],\n f: (x: T) => Promise<U>\n): Promise<U[]> {\n return Promise.all(xs.map(f));\n}\n\nexport async function partitionAsync<A, B extends boolean>(\n items: readonly A[],\n pred: (a: A) => Promise<B>\n): Promise<readonly [A[], A[]]> {\n const results = await Promise.all(items.map(pred));\n\n const yes: A[] = [];\n const no : A[] = [];\n\n for (let i = 0; i < items.length; i++) {\n (results[i] ? yes : no).push(items[i] as A);\n }\n return [yes, no] as const;\n}\n\n/**\n * remove a substring from the beginning of a string; throw if {@link str} does not start with {@link first}\n *\n * @example\n * withoutFirstSubstring('e', 'ego') // => 'go'\n */\nexport function withoutFirstSubstring(first: string, str: string) {\n if (!str.startsWith(first))\n throw new Error(`expected \"${str}\" to start with \"${first}\"`)\n return str.slice(first.length)\n}\n\nexport function isDefined<T>(x: T|null|undefined): x is NonNullable<T> {\n return x != null;\n}\n\nexport function isEmpty<T>(xs: readonly T[]): xs is [] {\n return xs.length === 0\n}\n\nexport function isNonEmpty<T>(xs: readonly T[]): xs is [T, ...T[]] {\n return xs.length >= 1;\n}\n\nexport function isSingle<T>(xs: readonly T[]): xs is [T] {\n return xs.length === 1;\n}\n\nexport function hasAtleastTwo<T>(xs: readonly T[]): xs is [T, T, ...T[]] {\n return xs.length >= 2;\n}\n\nexport function assertNever(_: never): never {\n throw new Error(\"unhandled variant\");\n}\n\n/**\n * whether the argument is a {@link Record}\n */\nexport function isRecord<K extends keyof any>(value: unknown): value is Record<string,K> {\n if (\n typeof value !== 'object'\n || value === null\n || Array.isArray(value)\n )\n return false\n\n return (Object.getOwnPropertySymbols(value).length === 0)\n}\n\n/**\n * remove a file or directory recursively; ignore errors\n */\nexport async function rm_rf(path: string): Promise<void> {\n return rm(path, {recursive: true}).catch(() => {})\n}\n\n/**\n * if the regex matches the string exactly once, return the match; otherwise throw\n */\nexport function getMatch(re: RegExp, str: string): string {\n const matches = str.match(re) || []\n if (matches===undefined || !isSingle(matches))\n throw new Error(`regex /${re.source}/ did not match '${str}' exactly once`)\n return matches[0]\n}\n\n/**\n * the `.trim()` method as a function\n *\n * `trim(str) <=> str.trim()`\n */\nexport function trim(str: string): string {\n return str.trim()\n}\n","import { isRecord } from \"./misc.js\"\n\n/**\n * walk {@link root} recursively while collecting all {@link T}s for which {@link pred} returnes a defined result\n *\n * @example\n * const root = {\n * A: 1,\n * b: 0,\n * c: {\n * 99: [{A:2}, {e:\"zero\"}],\n * A: 3\n * }\n * }\n * function pred(k: string, v: any): number|undefined {\n * if (k === 'A' && typeof v === 'number') { return v }\n * return undefined\n * }\n * const result = extract<number>(root, pred)\n * // result === [1, 2, 3]\n */\nexport function extract<T>(\n root: unknown,\n pred: (k: string, v: any) => T|undefined\n): T[] {\n\n function handleEntry([k,v]: [string,unknown]): T[] {\n const r = pred(k,v)\n return (r !== undefined) ? [r] : go(v)\n }\n\n function go(node: unknown): T[] {\n\n const ret =\n Array.isArray (node) ? flatMapArr_(node, go)\n : isRecord(node) ? flatMapRec_(node, handleEntry)\n : [] // null, primitives, functions, other records\n return ret\n }\n\n return go(root)\n}\n\nfunction flatMapArr_<U,T>(\n xs: U[],\n fn: (x:U) => T[]\n): T[] {\n return xs.flatMap(fn)\n}\n\nfunction flatMapRec_<T>(\n rec: Record<string,unknown>,\n fn : (t:[string,unknown]) => T[]\n): T[] {\n return Object.entries(rec).flatMap(fn)\n}\n","import { safeIndex } from '@carlwr/typescript-extra';\nimport * as fc from 'fast-check';\n\n/**\n * Randomly choose one of the constant array values.\n *\n * Shrinking is done towards the first element.\n *\n * Similar to {@link fc.constantFrom}, but _shrinks across all elements_ - {@link fc.constantFrom} only shrinks towards the first element.\n *\n * @example\n * import * as fcu from 'fastcheck-utils'\n * import * as fc from 'fast-check'\n *\n * const arb = fcu.element(['a', 'b', 'c'] as const)\n * const samples = fc.sample(arb, {seed:1, numRuns:3})\n * console.log(samples) // ['b', 'c', 'b']\n */\nexport function element<T>(xs: readonly [T, ...T[]]): fc.Arbitrary<T> {\n const arb = fc.nat({max: xs.length - 1})\n return arb.map(i => safeIndex(xs, i))\n}\n","import * as fc from 'fast-check'\n\nexport { infiniteStream, getNext }\nexport type { InfiniteStream }\n\ninterface InfiniteStream<T> extends fc.Stream<T> {}\n\n/**\n * Generate an infinite stream of values.\n *\n * This arbitrary is a minimal wrapper around {@link fc.infiniteStream} allowing access to the generated values in a type-safe way through the {@link getNext} helper.\n *\n * features and non-features:\n * - does _not_ shrink at all unfortunately - since {@link fc.infiniteStream} doesn't\n * - _does_ print a meaningful counterexample and execution summary on failure, that includes some of the previously tried values in the stream\n *\n * @example\n * import * as fcu from 'fastcheck-utils'\n * import * as fc from 'fast-check'\n *\n * const arb = fcu.infiniteStream(fc.nat({max:10}))\n * const stream = fc.sample(arb, {seed:1})[0] ?? fail()\n * console.log(fcu.getNext(stream)) // 8\n * console.log(fcu.getNext(stream)) // 2\n */\nfunction infiniteStream<T>(arb: fc.Arbitrary<T>): fc.Arbitrary<InfiniteStream<T>> {\n return fc.infiniteStream(arb) as fc.Arbitrary<InfiniteStream<T>>\n}\n\n/**\n * Get next value from an {@link InfiniteStream} object yielded by {@link infiniteStream}.\n *\n * Throws if the stream is unexpectedly done (it is my understanding that this should never happen).\n */\nfunction getNext<T>(stream: InfiniteStream<T>): T {\n const {value, done} = stream.next()\n if (done) {\n throw new Error('infinite stream unexpectedly done')\n }\n return value\n}\n","import * as fc from 'fast-check';\n\nexport { nonEmptyArray }\n\n/**\n * Generate a non-empty array.\n *\n * If a {@link constraints} parameter object is passed, it will be honored (function throws if `{minLength: 0}` is specified).\n *\n * @example\n * import * as fcu from 'fastcheck-utils'\n * import * as fc from 'fast-check'\n *\n * const arb = fcu.nonEmptyArray(fc.nat({max:5}))\n * const sample = fc.sample(arb, {seed:1})[0]\n * console.log(sample) // [5, 4, 2, 2, 5, 0, 3, 1, 5, 3, 1]\n */\nfunction nonEmptyArray<T>(\n arb : fc.Arbitrary<T>,\n constraints?: fc.ArrayConstraints\n): fc.Arbitrary<[T,...T[]]> {\n\n if (constraints?.minLength === 0) {\n throw new Error(\"minLength cannot be 0 for non-empty array\");\n }\n\n const base = constraints ?? { minLength: 1 };\n const constr: fc.ArrayConstraints = base.minLength !== undefined\n ? base\n : { ...base, minLength: 1 };\n\n return fc.array(arb, constr) as fc.Arbitrary<[T,...T[]]>\n}\n","import * as fc from 'fast-check';\n\n\n/**\n * like {@link fc.record}, but with\n * - `noNullPrototype` _true_ by default\n * - stronger typing\n *\n * @example\n * import * as fcu from 'fastcheck-utils'\n * import * as fc from 'fast-check'\n *\n * const arb = fcu.record({name: fc.string(), age: fc.nat({max: 100})})\n * const sample = fc.sample(arb, {seed:1})[0] ?? fail()\n * console.log(sample) // {name: 'TVb~o\"nP', age: 36}\n */\nexport function record<T>(\n model: Model<T>\n): fc.Arbitrary<ExactRecord<T>>\n\nexport function record<T>(\n model : Model<T>,\n constr: AllKeysRequired<T>\n): fc.Arbitrary<ExactRecord<T>>\n\nexport function record<T, K extends keyof T>(\n model : Model<T>,\n constr : SomeKeysRequired<T,K>\n): fc.Arbitrary<fc.RecordValue<T,K>>\n\nexport function record<T, K extends keyof T = keyof T>(\n model : Model<T>,\n constr?: fc.RecordConstraints<K>\n):| fc.Arbitrary<ExactRecord<T>>\n | fc.Arbitrary<fc.RecordValue<T,K>>\n {\n\n const nnp = constr?.noNullPrototype ?? true\n\n if (!constr?.requiredKeys) {\n // No constraints or no requiredKeys specified - default to all keys required\n const constr_ = { noNullPrototype: nnp }\n return fc.record(model, constr_) as fc.Arbitrary<ExactRecord<T>>\n }\n\n const constr_ = {\n noNullPrototype: nnp,\n requiredKeys : constr.requiredKeys\n }\n\n const arb = fc.record(model, constr_)\n\n const allKeys = Object.keys(model)\n const typedArb = isAllKeysRequired(constr.requiredKeys, allKeys)\n ? arb as fc.Arbitrary<ExactRecord<T>>\n : arb as fc.Arbitrary<fc.RecordValue<T,K>>\n\n return typedArb\n}\n\nfunction isAllKeysRequired(\n requiredKeys: PropertyKey[],\n allKeys : PropertyKey[]\n): boolean {\n return (\n requiredKeys.length === allKeys.length &&\n allKeys.every(requiredKeys.includes)\n )\n}\n\ntype Model<T> = { [K in keyof T]: fc.Arbitrary<T[K]> }\ntype ExactRecord<T> = { [K in keyof T]: T[K] }\n\ntype AllKeysRequired<T> =\n fc.RecordConstraints<keyof T> &\n { requiredKeys: readonly (keyof T)[] }\n\ntype SomeKeysRequired<T, K extends keyof T> =\n fc.RecordConstraints<K> &\n { requiredKeys: readonly K[] }\n","import { isNonEmpty } from \"@carlwr/typescript-extra\";\nimport * as fc from 'fast-check';\n\nexport { nonEmptyUniqueArray }\n\n/**\n * Generate a non-empty array of unique values.\n *\n * @example\n * import * as fcu from 'fastcheck-utils'\n * import * as fc from 'fast-check'\n *\n * const arb = fcu.nonEmptyUniqueArray(fc.nat({max:10}))\n * const sample = fc.sample(arb, {seed:1})[0]\n * console.log(sample) // [2, 6, 5, 9, 4, 7, 10, 3, 1, 0, 8]\n */\nfunction nonEmptyUniqueArray<T, U = T>(\n arb : fc.Arbitrary<T>,\n constraints?: fc.UniqueArrayConstraints<T, U>\n): fc.Arbitrary<[T,...T[]]> {\n\n const base = constraints ?? {} as fc.UniqueArrayConstraints<T, U>;\n const constr: fc.UniqueArrayConstraints<T, U> = {\n ...base,\n minLength: Math.max(base.minLength ?? 1, 1)\n };\n\n const ret = fc.uniqueArray(arb, constr).filter(isNonEmpty)\n return ret as fc.Arbitrary<[T,...T[]]>\n}\n"],"mappings":"AA4FO,SAASA,EAAaC,EAA0BC,EAAc,CACnE,GAAIA,EAAE,GAAKA,GAAGD,EAAG,OACf,MAAM,IAAI,MAAM,SAASC,CAAC,sCAAsCD,EAAG,MAAM,EAAE,EAE7E,OAAOA,EAAGC,CAAC,CACb,CAmDO,SAASC,EAAcC,EAAqC,CACjE,OAAOA,EAAG,QAAU,CACtB,CErJA,UAAYC,MAAQ,aAiBb,SAASC,EAAWC,EAA2C,CAEpE,OADe,MAAI,CAAC,IAAKA,EAAG,OAAS,CAAC,CAAC,EAC5B,IAAIC,GAAKC,EAAUF,EAAIC,CAAC,CAAC,CACtC,CCrBA,UAAYE,MAAQ,aAyBpB,SAASC,EAAkBC,EAAuD,CAChF,OAAU,iBAAeA,CAAG,CAC9B,CAOA,SAASC,EAAWC,EAA8B,CAChD,GAAM,CAAC,MAAAC,EAAO,KAAAC,CAAI,EAAIF,EAAO,KAAK,EAClC,GAAIE,EACF,MAAM,IAAI,MAAM,mCAAmC,EAErD,OAAOD,CACT,CCxCA,UAAYE,MAAQ,aAiBpB,SAASC,EACPC,EACAC,EAC0B,CAE1B,GAAIA,GAAa,YAAc,EAC7B,MAAM,IAAI,MAAM,2CAA2C,EAG7D,IAAMC,EAAOD,GAAe,CAAE,UAAW,CAAE,EACrCE,EAA8BD,EAAK,YAAc,OACnDA,EACA,CAAE,GAAGA,EAAM,UAAW,CAAE,EAE5B,OAAU,QAAMF,EAAKG,CAAM,CAC7B,CChCA,UAAYC,MAAQ,aA8Bb,SAASC,EACdC,EACAC,EAGA,CAEA,IAAMC,EAAMD,GAAQ,iBAAmB,GAEvC,GAAI,CAACA,GAAQ,aAGX,OAAU,SAAOD,EADD,CAAE,gBAAiBE,CAAI,CACR,EAGjC,IAAMC,EAAU,CACd,gBAAiBD,EACjB,aAAiBD,EAAO,YAC1B,EAEMG,EAAS,SAAOJ,EAAOG,CAAO,EAE9BE,EAAU,OAAO,KAAKL,CAAK,EAKjC,OAJiBM,EAAkBL,EAAO,aAAcI,CAAO,EAC3DD,CAIN,CAEA,SAASE,EACPC,EACAF,EACS,CACT,OACEE,EAAa,SAAWF,EAAQ,QAChCA,EAAQ,MAAME,EAAa,QAAQ,CAEvC,CCnEA,UAAYC,MAAQ,aAepB,SAASC,EACPC,EACAC,EAC0B,CAE1B,IAAMC,EAAOD,GAAe,CAAC,EACvBE,EAA0C,CAC9C,GAAGD,EACH,UAAW,KAAK,IAAIA,EAAK,WAAa,EAAG,CAAC,CAC5C,EAGA,OADe,cAAYF,EAAKG,CAAM,EAAE,OAAOC,CAAU,CAE3D","names":["safeIndex","xs","i","isNonEmpty","xs","fc","element","xs","i","U","fc","infiniteStream","arb","getNext","stream","value","done","fc","nonEmptyArray","arb","constraints","base","constr","fc","record","model","constr","nnp","constr_","arb","allKeys","isAllKeysRequired","requiredKeys","fc","nonEmptyUniqueArray","arb","constraints","base","constr","N"]}
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@carlwr/fastcheck-utils",
3
+ "description": "improved generators for fast-check",
4
+ "keywords": [
5
+ "fast-check",
6
+ "arbitraries",
7
+ "generators"
8
+ ],
9
+ "author": {
10
+ "name": "carlwr",
11
+ "url": "https://github.com/carlwr"
12
+ },
13
+ "publisher": "carlwr",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/carlwr/fastcheck-utils.git"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/carlwr/fastcheck-utils/issues"
20
+ },
21
+ "homepage": "https://github.com/carlwr/fastcheck-utils#README.md",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "version": "0.2.0",
26
+ "license": "MIT",
27
+ "type": "module",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "import": {
33
+ "types": "./dist/index.d.ts",
34
+ "default": "./dist/index.js"
35
+ }
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "LICENSE",
41
+ "README.md"
42
+ ],
43
+ "peerDependencies": {
44
+ "fast-check": ">=3.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@biomejs/biome": "^1.9.4",
48
+ "@carlwr/typescript-extra": "0.2.0",
49
+ "@fast-check/vitest": "^0.2.1",
50
+ "@types/node": "^22.15.21",
51
+ "arg": "^5.0.2",
52
+ "read-pkg": "^9.0.1",
53
+ "tsup": "^8.5.0",
54
+ "tsx": "^4.19.4",
55
+ "typedoc": "^0.28.5",
56
+ "typedoc-plugin-markdown": "^4.6.4",
57
+ "typescript": "^5.8.3",
58
+ "vitest": "^3.1.4",
59
+ "zod": "^3.25.56",
60
+ "zzz_LAST_dummy": "npm:empty-npm-package@1.0.0"
61
+ },
62
+ "engines": {
63
+ "node": ">=20.0.0"
64
+ },
65
+ "scripts": {
66
+ "build": "tsx build.ts",
67
+ "build:dev": "tsx build.ts --dev",
68
+ "prebuild": "pnpm run lint && pnpm run typecheck",
69
+ "publish:dry": "pnpm publish --dry-run",
70
+ "publish:release": "pnpm publish",
71
+ "version:patch": "pnpm version patch",
72
+ "version:minor": "pnpm version minor",
73
+ "version:major": "pnpm version major",
74
+ "lint": "biome check",
75
+ "imports:sort": "biome check --linter-enabled=false --organize-imports-enabled=true --write",
76
+ "typecheck": "tsc --noEmit",
77
+ "test": "vitest run",
78
+ "test:watch": "vitest",
79
+ "qa": "pnpm typecheck && pnpm lint && pnpm test",
80
+ "readme": "tsx scripts/makeReadme.ts",
81
+ "LAST_dummy": "false"
82
+ }
83
+ }