Creating a subclass in JavaScript ES6+ that directly returns an object within its constructor

I am facing an issue with extending a Javascript ES6+ class that returns an object from the constructor.

Consider the following scenario:

class Base {
  constructor() {
    return {name: "base"}
  }
}

class Extension extends Base {
  constructor() {
    super()
  }

  saySomething() {
    console.log("What do you want me to say?")
  }
}

When creating a new instance of the Extension class and calling saySomething, it fails with an error. This issue arises because, for some reason related to the base class returning a value in its constructor, the method saySomething does not seem to exist on the Extension class prototype (only properties from the base class are present).

The root cause of this problem stemmed from my attempt to extend a PrismaClient as explained here. This led to certain methods on the PrismaService class not being available on the prototype chain.


function PrismaExtended() {
  type Params = ConstructorParameters<typeof PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>>;
  const client = (...args: Params) =>
    new PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>(...args).$extends({
      result: {
        $allModels: {
          toJSON: {
            compute(data: BaseEntity) {
              return <T extends typeof data = typeof data>() => {
                return Object.assign({}, data, {
                  sn: typeof data.sn === 'bigint' ? data.sn.toString() : data.sn,
                }) as unknown as Omit<T, 'sn'> & { sn: string };
              };
            },
          },
        },
      },
    });

  class PrismaExtended {
    constructor(...args: Params) {
      return client(...args);
    }
  }

  // Object.setPrototypeOf(PrismaExtended, PrismaClient.prototype);
  return PrismaExtended as new (...args: Params) => ReturnType<typeof client>;
}

@Injectable()
export class PrismaService extends PrismaExtended() implements OnModuleInit, OnModuleDestroy {
  private retries: number = 0;
  constructor(
    private readonly logger: AppLogger,
    configService: ConfigService,
  ) {
    super({
      log: pickFrom(configService, 'app.status') !== NodeEnv.PROD ? ['query'] : undefined,
      transactionOptions: { timeout: 60_000 },
    });
  }

  /**
   * Check if an error is as a result of a precodition necessary to run an
   * operation not being matched.
   *
   * @param e The error to check
   * @returns boolean
   */
  isUnmatchedPrecondition(e: unknown): e is PrismaClientKnownRequestError {
    return e instanceof PrismaClientKnownRequestError && e.code === PrismaErrorCode.UNMATCHED_PRECONDITION;
  }
}

In this context, the method isUnmatchedPrecondition and any other additional methods do not exist at runtime.

Answer №1

Successfully implementing Proxy has enabled me to make this work.

Through the use of double Proxies, one specifically for intercepting object construction when the new keyword is applied to PrismaService, and due to the fact that the constructed object from the initial Proxy, meant to inherit from PrismaClient, lacked any methods or properties of the "supposed base class", I had to store a reference to the supposed base class and maintain a proxy to the constructed child class. This way, when there is an attempt to access a method or property on the child class instance that does not exist, it is redirected to the supposed base class, referenced in the outermost proxy where getClient was called.


function PrismaExtended() {
  type Params = ConstructorParameters<typeof PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>>;
  const getClient = (...args: Params) => {
    return new PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>(...args).$extends({
      result: {
        $allModels: {
          toJSON: {
            compute(data: BaseEntity) {
              return <T extends typeof data = typeof data>() => {
                return Object.assign({}, data, {
                  sn: typeof data.sn === 'bigint' ? data.sn.toString() : data.sn,
                }) as unknown as Omit<T, 'sn'> & { sn: string };
              };
            },
          },
        },
      },
    });
  };

  return new Proxy(class {}, {
    construct(target, args, newTarget) {
      const client = getClient(...args);
      const constructed = Reflect.construct(target, args, newTarget);

      return new Proxy(constructed, {
        get(target, key) {
          if (key in target) return target[key];
          return client[key];
        },
      });
    },
  }) as unknown as new (...args: Params) => ReturnType<typeof getClient>;
}

At present, this approach appears to be functioning well without any issues reported. However, in case you observe any oversights or potential pitfalls with this solution, please feel free to bring them to my attention.

I extend my gratitude to all those who assisted me throughout this process. Thank you.

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

Database query not appearing on node.js route

As a newcomer to javascript and nodejs, I have encountered an issue that I'm hoping to get some clarification on. I am currently working on a simple API and facing difficulties in understanding why a certain behavior is occurring in my code. app.get( ...

Using a Function as an Argument to Return an Unnamed Object

There seems to be a common trend in JavaScript code where a function is passed as a parameter, which in turn returns an anonymous object. myFunction(function() { return { foo: 'bar' }; }); What benefits or reasons are there for u ...

Dropdown with multiple selections organized in a hierarchical structure

I am in need of the following : Implement a drop-down menu that reflects hierarchical parent-child relationships. Include a checkbox for each node to allow for selection. If all child nodes are selected, their parent node should be automatically ch ...

The parameter value experiences an abrupt and immediate transformation

I recently created an electron app using Node.js and encountered a peculiar issue that I am unable to resolve: Below is the object that I passed as input: { lessons: [ name: "math", scores: [90, 96, 76], isEmpty: false ] } ...

Failure to Trigger jQuery.ajax Success Callback Function

My JavaScript Ajax call using jQuery.ajax is experiencing an issue where the success callback function does not execute. $.ajax({ url: target, contentType: 'application/json; charset=utf-8', type: 'POST', ...

What is the best way to implement a prev.next function in an image gallery using an array in JavaScript?

In the image gallery, you can simply click on each image to view a larger version. The enlarged image sources are pulled from the background-image of the smaller images. What I am aiming to achieve is the addition of previous and next buttons for easy navi ...

Displaying postcode on a category page: A step-by-step guide

I would like to showcase the user input code and present it on the web exactly as entered. <?php #code... ?> Any assistance is greatly appreciated. Please excuse my English. Thank you! ...

The button labeled "issett" is malfunctioning

Hey there, so I've created a registration form using jQuery and PHP that validates user input in real-time as they type. Once all the information is correct, I want to allow the user to submit the form. My plan is to write an if statement that checks ...

Determine the exact moment at which the value shifts within the table

Looking for assistance in automating the grade calculation process whenever there is a change in table values. Any suggestions on how to achieve this? I am new to events and AJAX, so any help would be appreciated as I am still learning. Please see the ima ...

Transforming my asynchronous code into a synchronous flow using setTimeout. Should I consider implementing promises?

I have a project in which I am creating a data scraper for a specific website. To ensure that I make a request only every 10 seconds, I have set up a setTimeout loop. This loop takes a URL as a parameter from an array of URLs that I manually input. In the ...

Instead of constantly updating my stateful component, I prefer to create a new paragraph for each new state

Each time the Create Sales Order Button is clicked, an API call is made and the response is printed as a Stateful Component. Rather than updating the existing component, I want to generate a new paragraph for each response received so that if the user clic ...

Checking the formik field with an array of objects through Yup for validation

Here is a snippet of the code I'm working on: https://codesandbox.io/s/busy-bose-4qhoh?file=/src/App.tsx I am currently in the process of creating a form that will accept an array of objects called Criterion, which are of a specific type: export inte ...

When it comes to assigning a background to a div using jQuery and JSON

I have been experimenting with creating a database using only JSON and surprisingly, it worked once I added a "js/" in the URL. However, my current issue lies with CSS. Let me elaborate. Here is the JSON data: [ { "title":"Facebook", ...

Using Rails' link_to method within Bootstrap modals

I'm trying to implement a voting system where users can vote on different items displayed in a Bootstrap modal. Each item is listed as a button on the index page, and when clicked, it opens up the modal for voting. However, I'm facing challenges ...

Enter key always causes the Bootstrap form to submit

I am working with a jquery function: $("#get-input").keyup(function (event) { if (event.keyCode === 13) { $("#get-data").click(); } }); $("#get-data").click(function (e) { var endpoint = $(".get-input").val(); if ($('#data-d ...

Exciting Funnel Design with Highcharts

As someone new to the world of Highcharts library, I am eager to create a horizontal funnel. I noticed there is an example of a vertical funnel on http://www.highcharts.com/demo/funnel but I couldn't find any options to make it appear horizontal. I&ap ...

When using `element.addEventListener`, event.target will be the target

When working on a solution, I initially bound event listeners to multiple targets within the same container. I am curious to know if anyone has noticed considerable performance improvements by using just one event listener and leveraging the target of the ...

What is the best way to clear a THREE.JS scene?

I am exploring methods to remove all objects from a scene without affecting the scene structure. I understand that naming each object individually allows for selective deletion by name. But, I am searching for a fast approach to clear a scene of all obje ...

requesting data and receiving a promise object

I developed a function called getCartItems that invokes getSingleItems with the ID as an argument. When I log the JSON result in getSingleItem, it correctly displays the product object. However, when I try to access the function call value, I am getting a ...

Troubleshooting problems with AngularJS placeholders on Internet Explorer 9

On my partial page, I've included a placeholder like this: <input name="name" type="text" placeholder="Enter name" ng-class="{'error':form.name.$invalid}" ng-model="Name" required /> I have also set up client side validation for the ...