Creating a function type in Typescript that changes based on the value of a parameter

Recently, I've been working on creating a factory for my models in Typescript using the faker package. I was able to develop a functional generic factory similar to the casual package API, which takes a generic model maker and options to customize the created model. This generic factory generates another factory specifically for models. The generated factory can accept two parameters: the first one being the quantity of models to create (default is 1), and the second parameter being the options to be applied to the models. However, I'm facing a challenge in determining the return type of the factory based on the quantity value automatically. Essentially, if the quantity is one, I should return 'IModel', but if it's more than one, I should return 'IModel[]'.

Currently, I am explicitly returning 'IModel | IModel[]', which requires me to specify the return type whenever I utilize the factories:

jest.spyOn(registerUserStub, 'execute').mockResolvedValueOnce(userFactory(1) as IUserModel)

Below is a snippet of my code:

// My User Model
export type IUserModel = {
  id: string,
  name: string,
  email: string,
  password: string,
  active: boolean,
  confirmed: boolean
}

Factory Maker

import { DeepPartial } from 'utility-types'

export function factoryMaker<T = any> (objMaker: (options?: DeepPartial<T>) => T): (quantity: number, options?: DeepPartial<T>) => T | T[] {
  return (quantity, options) => {
    const entitiesArray = new Array(quantity).fill(null).map(() => objMaker(options))
    return quantity === 1 ? entitiesArray[0] : entitiesArray
  }
}

My User Factory


import { DeepPartial } from 'utility-types'
import faker from 'faker'

import { IUserModel } from '../models'
import { factoryMaker } from './factoryMaker'

type OptionsType = DeepPartial<IUserModel>

function makeUser (options?: OptionsType):IUserModel {
  return {
    id: faker.random.uuid(),
    password: faker.random.uuid(),
    email: faker.internet.email(),
    name: faker.name.findName(),
    confirmed: options.confirmed !== undefined ? options.confirmed : true,
    active: true,
    ...options
  }
}

const userFactory = factoryMaker<IUserModel>(makeUser)

export { userFactory }

Answer №1

To achieve this functionality, consider modifying the factoryMaker function to return N extends 2 ? T : T[], with N representing the quantity required:

const createEntityFactory = <T>(
  entityCreator: (options?: Partial<T>) => T
): <N extends number>(
  quantity: N,
  options?: Partial<T>
) => N extends 2 ? T : T[] {
  return <N extends number>(
    quantity: N,
    options?: Partial<T>
  ): N extends 2 ? T : T[] => {
    const entityArray = new Array(quantity).fill(null).map(() => entityCreator(options));
    return (quantity === 1 ? entityArray[0] : entityArray) as N extends 2 ? T : T[];
  };
}

// Create one entity of type TEntity
const singleEntity = createEntityFactory(1);

// Create two entities of type TEntity
const multipleEntities = createEntityFactory(2);

// Create either one or two entities randomly
const randomEntities = createEntityFactory(Math.random() > 0.5 ? 1 : 2);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Checking the types of arrays does not function properly within nested objects

let example: number[] = [1, 2, 3, 'a'] // this code block correctly fails due to an incorrect value type let example2 = { demo: 1, items: <number[]> ['a', 'b'], // this code block also correctly fails because of ...

Determining whether an option value has been selected in Angular

I am working on a template that includes mat-autocomplete for element searching, with individual option elements displayed. I am trying to implement logic where if an element is selected, the input should be disabled. How can I determine if a specific elem ...

Does the router navigate function instantly update the router URL?

I'm testing whether the navigate function will immediately alter the router URL upon execution. this.router.navigate(['/home/products']); if (this.router.url.includes('/home/products')) console.log('URL has been changed&apos ...

Transform a collection of objects into instances of a class

I have a scenario where I am fetching an array of objects from PHP and my goal is to transform this data into instances of a class called ContentData. The current solution that I have seems to work fine, but deep down I sense that there might be a more el ...

Having difficulty transferring information from the component to the service

I am having trouble passing a parameter to a function that is defined in a service. No matter what I try, the parameter always ends up being undefined. login.component.ts import { Component, OnInit } from '@angular/core'; import { Authenticati ...

Unable to load dynamic JSON data in ag-grid for Angular 2

ngOnInit(){ this.gridOptions = {}; this.gridOptions.rowData = []; this.gridOptions.rowData = [ {configName: 1, configName1: "Jarryd", configName2: "Hayne", configName3: "tttttt", configName4: "rrrtttt", configName5:"drrrrrr"}]; } ...

Utilizing an external type definition in JSDoc @typedef

I'm encountering an issue with reducing redundancy when defining my imported types. I am trying to streamline the process, but unfortunately I am running into errors. /** @typedef {import("../types/util")} util @typedef {util.mapBehaviors} m ...

Looking to incorporate an additional column 'LastName' that can be used for filtering in the Angular data table code. This column will be included if it is present in the data

function applyFilter(filterValue: string) { filterValue = filterValue.toLowerCase(); --- return filtered result return this.dataSet.filter( (item: any) => item.name ? item.name.toLowerCase(). ...

Troubleshooting asynchronous functions in Ionic3 when using Firebase Storage and Firestore

Attempting to retrieve the downloadURL from an uploaded image. The uploadImage function is used to upload the image to Firebase Storage. uploadImage() { this.image = 'movie-' + new Date().getTime() + '.jpg'; let storageRef: any, ...

Error TS5023 occurs when the integrated terminal in Visual Studio Code encounters an unfamiliar option 'w' while executing the tsc -w command

I am encountering an issue with the tsc -w command in the VS Code integrated terminal. However, I have successfully executed it from the NodeJs Command Prompt. `error TS5023: Unknown option 'w' Use the '--help' flag to view available o ...

Is there a potential issue in Next.js 14 when utilizing the "useClient" function alongside conditional rendering in the app/layout.tsx file?

Within my app, there is a Navbar that will only be visible when the route is either "/" or "/teachers". The Navbar will not appear on the dashboard page ("/dashboard"). I achieved this using conditional rendering in the app/layout.tsx file. "use clien ...

Switching buttons with AngularJS

I am currently working on a Github search app using the Github API in Angular. My goal is to make it so that when the user clicks the "Add to Favorite" button, the button disappears and the "Remove Favorite" button is displayed instead. I attempted to achi ...

Library types for TypeScript declaration merging

Is there a way to "extend" interfaces through declaration merging that were originally declared in a TypeScript library file? Specifically, I am trying to extend the HTMLCanvasElement interface from the built-in TypeScript library lib.dom. While I underst ...

"Exploring the world of jest.doMock: A guide to mocking

Here is the code snippet I am testing: ... import data from '../data/mock.json'; // function is async export const something = async () => { try { ... if (!data) { throw 'error is here!'; } ...

How come Typescript claims that X could potentially be undefined within useMemo, even though it has already been defined and cannot be undefined at this stage

I am facing an issue with the following code snippet: const productsWithAddonPrice = useMemo(() => { const addonsPrice = addonsSelected .map(id => { if (addons === undefined) { return 0} return addons.find(addon => addo ...

Tips on preventing the need for null or undefined checks in JS/Typescript singletons that have an initialization function

Is there a way to streamline the process of handling props in an Object literal that is dynamically initialized only once? I'm looking for a pattern that would eliminate the need for repetitive null/undefined checks and throw errors when certain metho ...

Storing information retrieved from the API for use in different modules

Trying to extract data from a WEB API service using Angular 8 has been quite challenging for me. A service I created makes the API call: return this.http.get<UserSession>(uri) .pipe(map((json: UserSession) => this.EntryFormAdapter(json))); Th ...

Does the term 'alias' hold a special significance in programming?

Utilizing Angular 2 and Typescript, I have a component with a property defined as follows: alias: string; Attempting to bind this property to an input tag in my template like so: <input class="form-control" type="text" required ...

The ngx-image-cropper in Angular only necessitates a button click, making the default cropper unnecessary

Currently, the image is cropped by default when loaded, but I would like the crop to only occur when the crop button is clicked. I tried searching on Google and found autoCrop:false, but I am unsure where to place it in the code. Below is the HTML code: ...

A loop in JavaScript/TypeScript that runs precisely once every minute

Here is a snippet of my code: async run(minutesToRun: number): Promise<void> { await authenticate(); await this.stock.fillArray(); await subscribeToInstrument(this, this.orderBookId); await subscribeToOrderbook(this, this.orderBookId ...