|
1 | 1 | export interface WeaveOptions<TInput> { |
| 2 | + /** |
| 3 | + * The amount of items to generate |
| 4 | + * @default 10 |
| 5 | + */ |
2 | 6 | amount: number; |
| 7 | + |
| 8 | + /** |
| 9 | + * A function that will be called for each item to be generated |
| 10 | + * @param {TInput} input - The input object |
| 11 | + * @param {number} index - The index of the item to be generated |
| 12 | + * @returns {TInput} The generated item |
| 13 | + */ |
3 | 14 | customizer: (input: TInput, index: number) => TInput; |
| 15 | + |
| 16 | + /** |
| 17 | + * A list of fields that will be used to generate random values |
| 18 | + * @type {{ [K in keyof Partial<TInput>]: Array<TInput[K]> }} |
| 19 | + */ |
| 20 | + fields?: { |
| 21 | + [K in keyof Partial<TInput>]: Array<TInput[K]>; |
| 22 | + }; |
4 | 23 | } |
5 | 24 |
|
6 | 25 | const DEFAULT_WEAVE_OPTIONS: WeaveOptions<any> = { |
7 | 26 | amount: 10, |
8 | 27 | customizer: (input) => input, |
| 28 | + fields: {}, |
9 | 29 | }; |
10 | 30 |
|
11 | | -export function weave<TInput>(input: TInput | TInput[], opts?: WeaveOptions<TInput>): TInput[] { |
12 | | - const { amount, customizer } = opts ?? DEFAULT_WEAVE_OPTIONS; |
| 31 | +export type EnsureValidWeaveInput<TInput> = |
| 32 | + TInput extends any[] |
| 33 | + ? TInput extends (infer TArrayInput)[] |
| 34 | + ? TArrayInput extends Record<string, unknown> |
| 35 | + ? TArrayInput[] |
| 36 | + : never |
| 37 | + : never |
| 38 | + : TInput extends Record<string, unknown> |
| 39 | + ? TInput |
| 40 | + : never; |
| 41 | + |
| 42 | +type RemoveArrayType<TInput> = TInput extends Array<infer TArrayInput> ? TArrayInput : TInput; |
| 43 | + |
| 44 | +/** |
| 45 | + * Weaves new objects into an array based on a template from the input array. |
| 46 | + * |
| 47 | + * @template TInput The type of the input array elements. |
| 48 | + * @param {EnsureValidWeaveInput<TInput>} input An array of objects to use as templates. |
| 49 | + * @param {WeaveOptions<RemoveArrayType<TInput>>} [opts] Optional configuration for the weaving process. |
| 50 | + * @param {number} [opts.amount] The number of new objects to weave into the array. Must be a positive number. |
| 51 | + * @param {WeaveFields<RemoveArrayType<TInput>>} [opts.fields] An object specifying fields to randomize in the new objects. Each field should be an array of possible values. |
| 52 | + * @param {WeaveCustomizer<RemoveArrayType<TInput>>} [opts.customizer] A function to customize each new object after field randomization. |
| 53 | + * @returns {RemoveArrayType<TInput>[]} A new array containing the original input array elements plus the woven objects. |
| 54 | + * @throws {Error} If the input is not a non-empty array, or if the amount is not a positive number. |
| 55 | + */ |
| 56 | +export function weave<TInput>(input: EnsureValidWeaveInput<TInput>, opts?: WeaveOptions<RemoveArrayType<TInput>>): RemoveArrayType<TInput>[] { |
| 57 | + const { amount, customizer, fields } = opts ?? DEFAULT_WEAVE_OPTIONS; |
| 58 | + |
| 59 | + if (!Array.isArray(input) || input.length === 0) { |
| 60 | + throw new Error("Input must be a non-empty array of objects"); |
| 61 | + } |
| 62 | + |
| 63 | + if (typeof amount !== "number" || amount <= 0) { |
| 64 | + throw new Error("Amount must be a positive number"); |
| 65 | + } |
13 | 66 |
|
14 | | - // convert single input to array |
15 | | - const inputs = Array.isArray(input) ? input : [input]; |
16 | | - const result: TInput[] = []; |
| 67 | + const result: RemoveArrayType<TInput>[] = [ |
| 68 | + ...input, |
| 69 | + ]; |
17 | 70 |
|
18 | 71 | for (let i = 0; i < amount; i++) { |
19 | | - for (const item of inputs) { |
20 | | - result.push(customizer(item, i)); |
| 72 | + const templateIndex = Math.floor(Math.random() * input.length) ?? 0; |
| 73 | + let template = { ...input[templateIndex] }; |
| 74 | + |
| 75 | + // apply random values from fields if provided |
| 76 | + if (fields && typeof fields === "object") { |
| 77 | + (Object.keys(fields) as Array<keyof RemoveArrayType<TInput>>).forEach((field) => { |
| 78 | + if (field in template && Array.isArray(fields[field]) && fields[field]!.length > 0) { |
| 79 | + const randomValueIndex = Math.floor(Math.random() * fields[field]!.length); |
| 80 | + template[field] = fields[field]![randomValueIndex]; |
| 81 | + } |
| 82 | + }); |
21 | 83 | } |
| 84 | + |
| 85 | + // apply customizer if provided |
| 86 | + if (customizer && typeof customizer === "function") { |
| 87 | + template = customizer(template, i); |
| 88 | + } |
| 89 | + |
| 90 | + result.push(template); |
22 | 91 | } |
23 | 92 |
|
24 | 93 | return result; |
|
0 commit comments