# MockThis
A powerful data generation library.

## Introduction

`MockThis` is a powerful tool for generating mock data for testing and development purposes, built with extensibility in mind. With its flexible schema definitions, variety of built-in generators, and the ability to extend functionality through plugins and custom generators, it can greatly simplify the process of creating realistic test data. With a fluid and intuitive API, `MockThis` allows you to easily define and generate complex data structures.

## Installation

You can install `MockThis` using npm:

```sh
npm install mockthis
```
## Usage

### Basic Example

Here is a basic example of how to use `MockThis` to generate mock data:

```typescript
import { MockThis, FirstName, LastName, Email, Sequence, DecimalRange, DateTime, Address, City, Country, Uuid, PhoneNumber, ZipCode } from "mockthis";

// Define the schema for the mock data
const schema = {
  id: Uuid,
  person: {
    first: FirstName,
    last: LastName,
    emails: [Email]
    phoneNumber: PhoneNumber,
  },
  address: {
    address: Address,
    city: City,
    state: State,
    zipCode: ZipCode
    country: Country
  },
  stats: {
    score: DecimalRange(0, 100),
    lastLogin: DateTime
  }
};

// Create and configure the MockThis instance
const data = await MockThis(schema)
  .setTotal(1, 5)
  .setArrayRange(1, 5)
  .setNullValueChance(0.1)
  .setFormats({
    date: "YYYY-MM-DD",
    time: "HH:mm:ss"
  })
  .asJson();
```
### Nesting Example
`MockThis` supports complex nesting of schemas and allows you to set array lengths directly within your schema declaration. This flexibility enables you to generate intricate and deeply nested mock data structures according to your specific requirements. The length of generated arrays can also be set in combination with the `setArrayRange` method.

Here is a basic example:
```typescript
const schema = {
    nested: {
        values: {
            singleValue: Constant("Nested Value"),
            arrayValues: [Animal, 1, 5] // This array will have between 1 and 5 values.
        },
        arrayception: [[Sequence(colors), 5], 5],
        arrayOfObjects: [{
            prop1: Word,
            prop2: Sentence,
            prop3: Paragraph
        }] // This array will use the global array range defined in setArrayRange.
    }
};

const data = await MockThis(schema)
  .setTotal(5)
  .setArrayRange(1, 10) // This range will apply to all generated arrays unless they have a range defined inline.
  .asJson();
```

## Methods and Their Usage

#### `MockThis<G>(schema: object, plugins?: MockThisPlugin[], generator?: G): MockThisInstance`

Creates a new `MockThisInstance` with the provided schema.

- **Parameters**:
  - `schema`: An object defining the structure and data types of the mock data.
  - `plugins`: An optional array of `MockThisPlugin` objects.
- **Returns**:
  - `MockThisInstance`: A new `MockThisInstance`.

**Usage**:

```typescript
const mockThis = MockThis(schema);
```

#### `setTotal(min: number, max?: number): this`

Sets the total number of top-level mock data objects to generate.

- **Parameters**:
  - `min`: The minimum number of items to generate.
  - `max?`: Optional. The maximum number of items to generate. If not provided, `min` is used.
- **Returns**:
  - `MockThisInstance`: The current `MockThisInstance`. This allows chaining of methods.

**Usage**:

```typescript
MockThis({}).setTotal(10); // Generates exactly 10 items
MockThis({}).setTotal(5, 15); // Generates between 5 and 15 items
```

#### `setArrayRange(min: number, max?: number): this`

Sets the range for lengths of arrays within the data.

- **Parameters**:
  - `min`: The minimum length of arrays.
  - `max?`: Optional. The maximum length of arrays. If not provided, `min` is used.
- **Returns**:
  - `MockThisInstance`: The current `MockThisInstance`. This allows chaining of methods.

**Usage**:

```typescript
MockThis({}).setArrayRange(2); // Arrays will have a length of 2
MockThis({}).setArrayRange(2, 5); // Arrays will have lengths between 2 and 5
```

#### `setFormat<K extends keyof Formats>(key: K, value: Formats[K]): this`

Sets format for provided key.

- **Parameters**:
  - `key`: The key of the format.
  - `value`: The value of the format.
- **Returns**:
  - `MockThisInstance`: The current `MockThisInstance`. This allows chaining of methods.

**Usage**:

```typescript
MockThis({}).setFormat("date", "DD/MM/YYYY");
```

#### `setFormats(formats: { [K in keyof Formats]?: Formats[K] }): this`

Sets formats for all provided keys.

- **Parameters**:
  - `formats`: An object specifying format strings for date and time.
- **Returns**:
  - `MockThisInstance`: The current `MockThisInstance`. This allows chaining of methods.

**Usage**:

```typescript
MockThis({}).setFormats({
  date: "DD/MM/YYYY",
  time: "HH:mm",
});
```

#### `setNullValueChance(chance: number): this`

Sets the probability that fields can be `null`.

- **Parameters**:
  - `chance`: A number between `0` and `1` representing the probability.
- **Returns**:
  - `MockThisInstance`: The current `MockThisInstance`. This allows chaining of methods.

**Usage**:

```typescript
MockThis({}).setNullValueChance(0.2); // 20% chance of null values
```

#### `setRequired(requiredFields: string[]): this`

Specifies fields that must not be `null`.

- **Parameters**:
  - `requiredFields`: An array of field names that are required.
- **Returns**:
  - `MockThisInstance`: The current `MockThisInstance`. This allows chaining of methods.

**Usage**:

```typescript
MockThis({}).setRequired(["id", "name"]);
```

#### `setUserDefinedBlueprint<T>(blueprint: T): this`

Adds user-defined values to the blueprint. These can be used in custom `TypeFunc` functions by accessing the the `userDefined` property on the `Blueprint` param.

- **Parameters**:
  - `blueprint`: An object containing custom fields.
- **Returns**:
  - `MockThisInstance`: The current `MockThisInstance`. This allows chaining of methods.

**Usage**:

```typescript
MockThis({}).setUserDefinedBlueprint({
  customField: "Custom Value",
});
```

#### `asJson(replacer?: Function, space?: string | number): Promise<string>`

Generates the mock data as a JSON string.

- **Parameters**:
  - `replacer?`: Optional. A function that alters the behavior of the stringification process.
  - `space?`: Optional. A string or number that's used to insert white space into the output JSON string.

- **Returns**:
  - `Promise<string>`: A promise that resolves to the JSON string.

**Usage**:

```typescript
const data = await MockThis({}).asJson(null, 2);
```

#### `asObject(): Promise<object[]>`

Generates the mock data as a JavaScript object or array of objects.

- **Returns**:
  - `Promise<object[]>`: A promise that resolves to the generated object(s).

**Usage**:

```typescript
const data = await MockThis({}).asObject();
```

## Built-in TypeFuncs

`MockThis` includes a variety of built-in data generators:

- **Basic Types**:
  - `Integer`: Random integer between -500 and 500.
  - `IntegerRange(min: number, max: number)`: Random integer between specified min and max.
  - `Decimal`: Random decimal number.
  - `DecimalRange(min: number, max: number)`: Random decimal between specified min and max.
  - `Bool`: Random boolean value.
  - `Constant<T>(value: T)`: Constant value.

- **Date and Time**:
  - `DateTime`: Random datetime.
  - `DateTimePast`: Random datetime in the past.
  - `DateTimeFuture`: Random datetime in the future.
  - `DateTimeBetween(start: string | Date, end: string | Date)`: Random datetime between two dates.
  - `Birthdate`: Random birthdate for an adult between 18 and 80 years old.
  - `FormatDateTime(typeFunc: TypeFunc<any>, format: string)`: Format another datetime TypeFunc.

- **Lorem Ipsum**
  - `Letter`: Random letter. 
  - `Word`: Random word.
  - `Sentence`: Random sentence.
  - `Paragraph`: Random paragraph.

- **Person**:
  - `FirstName`: Random first name.
  - `LastName`: Random last name.
  - `Email`: Random email address.
  - `PhoneNumber`: Random phone number.
  - `SocialSecurityNumber`: Random social security number.

- **Location**:
  - `Address`: Random street address.
  - `City`: Random city name.
  - `Country`: Random country name.
  - `State`: Random state or region.
  - `ZipCode`: Random postal code.
  - `Coordinates`: Random geographical coordinates.

- **Financial**:
  - `Currency`: Random currency code.
  - `Dollar`: Random dollar amount.
  - `Euro`: Random euro amount.

- **Web**:
  - `Url`: Random url.
  - `AvatarUrl`: Random avatar URL.

- **Company**:
  - `CompanyName`: Random company name.
  - `CatchPhrase`: Random company catchphrase.
  - `CatchPhraseAdjective`: Random adjective for a catchphrase.
  - `CatchPhraseDescriptor`: Random descriptor for a catchphrase.
  - `CatchPhraseNoun`: Random noun for a catchphrase.
  - `BuzzAdjective`: Random buzzword adjective.
  - `BuzzNoun`: Random buzzword noun.
  - `BuzzPhrase`: Random buzzword phrase.
  - `BuzzVerb`: Random buzzword verb.

- **Vehicle**:
  - `Vehicle`: Random vehicle.
  - `VehicleManufacturer`: Random vehicle manufacturer.
  - `VehicleModel`: Random vehicle model.
  - `VehicleType`: Random vehicle type.
  - `VehicleFuelType`: Random vehicle fuel type.

- **Music**:
  - `MusicAlbum`: Random music album name.
  - `MusicArtist`: Random music artist name.
  - `MusicGenre`: Random music genre.
  - `MusicSong`: Random song name.

- **Book**
  - `BookTitle`: Random book title.
  - `BookAuthor`: Random book author.
  - `BookGenre`: Random book genre.
  - `BookPublisher`: Random book publisher.
  - `BookFormat`: Random book format.
  - `BookSeries`: Random book series.

- **Food**:
  - `FoodAdjective`: Random food-related adjective.
  - `FoodDescription`: Random food description.
  - `FoodDish`: Random dish name.
  - `FoodEthnicCategory`: Random ethnic food category.
  - `FoodFruit`: Random fruit name.
  - `FoodIngredient`: Random ingredient name.
  - `FoodMeat`: Random meat name.
  - `FoodSpice`: Random spice name.
  - `FoodVegetable`: Random vegetable name.

- **Airline**:
  - `AircraftType`: Random aircraft type.
  - `Airline`: Random airline name.
  - `Airplane`: Random airplane name.
  - `Airport`: Random airport name.
  - `FlightNumber`: Random flight number.
  - `RecordLocator`: Random record locator.
  - `Seat`: Random seat assignment.

- **Misc**:
  - `Animal`: Random animal type.
  - `Url`: Random url.
  - `Color`: Random color.
  - `Element`: Random chemical element.

- **Utility**:
  - `Random<T>(items: T[])`: Random value from an array of values.
  - `Sequence<T>(items: T[])`: Value from an array. The returned index is incremented each time this `TypeFunc` returns a value.
  - `Id(max?: number)`: A new number id from a sequence. `max` defaults to 100000.
  - `Uuid`: A UUID (Universally Unique Identifier).
  - `EnumRandom(enumType)`: Random enum value.
  - `EnumSequence(enumType)`: Value from an enum. The returned index is incremented each time this `TypeFunc` returns a value.
  - `MapValue<T, U>(typeFunc: TypeFunc<T>, mapCallback: (value: T) => U) => TypeFunc<U>`: The value of the provided `TypeFunc` modified using the `mapCallback` function.
  - `ReduceValues<T>(typeFuncs: TypeFunc<any>[], reduceCallback: (values: any[]) => T) => TypeFunc<T>`: Combines the returned values of multiple `TypeFunc`s using the `reduceCallback` function.
  - `Async<T>(generator: () => Promise<T>)`: Async value.
  - `Dep<T>(dependencies: string[], (dependencies: Record<string, any>, getValue: (type: TypeFunc) => any) => T)`: The `Dep` function creates a custom generator that allows a field's value to be dependent on other fields in your schema. It's useful for generating values that are calculated based on other fields and where data consistency is desired. The second parameter `getValue` is a helper function to generate values from other `TypeFunc` generators if needed.

## Advanced Built-in TypeFuncs

You can create more complex generators that depend on other fields in the data, use external data sources, asynchronous operations, or other advanced logic.

#### Example: Dependency TypeFunc

```typescript
// Define a custom generator that depends on other fields
const FullNameGenerator = Dep(
  ["firstName", "lastName", "suffix.value"],
  (deps, getValue) => {
    const { firstName, lastName } = deps;
    const suffix = deps.suffix.value;
    const middleName = getValue(FirstName);
    return `${firstName} ${middleName} ${lastName} ${suffix}`;
  }
);

const schema = {
  firstName: FirstName,
  lastName: LastName,
  suffix: {
    value: Constant("III")
  }
  fullName: FullNameGenerator,
};

const data = await MockThis(schema).asObject();
```

#### Explanation

- **Dep**: The `Dep` function allows you to create a custom generator that depends on the values of other fields.
- **Dependencies**: You specify an array of dependency field names as the first argument to `Dep`. The values can be dot delimited to access nested properties.
- **Callback**: The second argument is a callback function that receives the values of the dependencies as well as a `getValue` helper function that can be used to generate values using other TypeFuncs.
- **Example**: In this example, `fullName` depends on `firstName` and `lastName` and combines them, along with another value generated as the middle name, to create the full name.

**NOTE**: `Dep` cannot be nested within another `Dep` function.

#### Example: Async TypeFunc

```typescript
// Define an asynchronous custom generator
const AsyncGenerator = Async(async () => {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();
  return data.value;
});

const schema = {
  asyncField: AsyncGenerator,
};

const data = await MockThis(schema).asObject();
```

#### Explanation

- **Async**: Wrap your async function with `Async` to create an asynchronous generator.

#### Example: MapValue TypeFunc

```typescript
// Map Decimal TypeFunc to 2 decimal places
const PrecisionDecimal = MapValue(Decimal, (value) => parseInt(value.toFixed(2)));

const schema = {
  precisionDecimal: PrecisionDecimal,
};

const data = await MockThis(schema).asObject();
```

#### Explanation
- **MapValue**: Transforms the returned value of the provided `TypeFunc` using the `mapCallback` function.

#### Example: ReduceValues TypeFunc

```typescript
// Combine the value of FirstName with the value of LastName
const FullName = ReduceValues([FirstName, LastName], ([firstName, lastName]) => `${firstName} ${lastName}`);

const schema = {
  fullName: FullName
};

const data = await MockThis(schema).asObject();
```

#### Explanation
- **ReduceValues**: Combines the returned values of multiple `TypeFunc`s using the `reduceCallback` function.

## Customs TypeFuncs

In addition to the built in data generators, you can create custom `TypeFunc` generators to produce new types of data.

#### Example: Custom TypeFunc

```typescript
// Define a custom generator function
const CustomType: TypeFunc<string> = (blueprint) => {
  // Custom logic to generate a value
  return "Custom Generated Value";
};

// Use the custom generator in your schema
const schema = {
  customType: CustomType
};

const data = await MockThis(schema).asObject();
```

### Explanation

- **TypeFunc**: Use `TypeFunc` to define a custom generator function.
- **Blueprint**: The config object used by `MockThis` in data generation. Contains core config as well as user defined config.
- **Use in Schema**: Use your custom generator in the schema like any other generator.

## Plugins

### Implementing Custom Plugins in MockThis

You can extend the functionality of **MockThis** by creating custom generators and plugins. This allows you to add new data generation methods or modify existing behavior to suit your specific needs.

### Creating a Custom Plugin

To create a custom plugin, you need to implement the `MockThisPlugin` interface. This interface allows you to register new methods on the `MockThisInstance` and `BlueprintBuilder` classes, as well as modify the generated data at various points in the generation process.

#### Example: `ExamplePlugin`

Here's an example of a custom plugin called `ExamplePlugin`:

```typescript
export class ExamplePlugin implements MockThisPlugin {
    registerMethods(instance: IMockThisInstance, blueprintBuilder: IBlueprintBuilder): void {
        blueprintBuilder.setPluginConfig = (config: Record<string, any>) => {
            blueprintBuilder.getBlueprint().pluginConfig = config;
        };

        instance.setPluginConfig = (config: Record<string, any>) => {
            blueprintBuilder.setPluginConfig(config);
            // Ensure that you return the instance when creating a method on `IMockThisInstance` to enable method chaining
            return instance;
        };
    }
    
    beforeSchemaPrepared(blueprint: IBlueprint, schema: Schema): Schema {
        // Apply transformation logic here
        return schema;
    }   

    afterSchemaPrepared(blueprint: IBlueprint, schemaItems: SchemaItem[]): SchemaItem[] {
        // Apply transformation logic here
        return schemaItems;
    }

    afterDataGenerated(blueprint: IBlueprint, schemas: Schema[]): Schema[] {
        // Apply transformation logic here
        return schemas;
    }
}

declare module 'mockthis' {
    interface IBlueprint {
        pluginConfig: Record<string, any>;
    }
    
    interface IBlueprintBuilder {
        setPluginConfig(config: Record<string, any>): void;
    }

    interface IMockThisInstance {
        setPluginConfig(config: Record<string, any>): this;
    }
}
```

#### Explanation

- **registerMethods**: This method allows you to add new methods to the `MockThisInstance` and `BlueprintBuilder`.
- **afterSchemaPrepared**: This method is called after the schema has been processed. You can modify the schema items `SchemaItem[]`.
- **afterDataGenerated**: This method is called after the data has been generated. You can modify the data before it is returned.

**NOTE**: Make sure to extend the necessary interfaces to enable TypeScript support.

### Using the Custom Plugin

To use your custom plugin, you need to pass an instance of it to the `MockThis` function:

```typescript
const schema = {
  id: Id(),
  name: FirstName
};

// Create and configure the MockThis instance with the plugin
const data = await MockThis(schema, [new ExamplePlugin()]) // Note: Plugins are applied sequentially
  .setPluginConfig({
      prop: "value"
  })
  .asObject();
```

#### Explanation
- **Pass Plugin to MockThis**: When creating the `MockThis` instance, pass an array of plugins as the second argument.
- **Use New Methods**: You can now use the new method added by your plugin (`setPluginConfig` in this example).

### Conclusion

By creating custom plugins and generators, you can extend **MockThis** to better fit your testing and development needs. Whether you're adding utility methods, modifying generated data, or creating specialized data generators, **MockThis** provides a flexible framework for mock data generation.