samedi 13 mars 2021

Metadata on methods of a class with copied properties changes on changing the metadata of the source class - Typescript

Sorry for the verbose title. I have a class MutateMe passed into a factory called FilterFactory by a decorator Decorator.

export const Decorator = (options?: DecoratorOptions) => <T extends Constructor>(target: T) => {
  new FilterFactory(target, options);
}

Within this factory, I'm copying over the methods onto the target class and setting its metadata.

export class FilterFactory {
  constructor(protected target: any, options: DecoratorOptions) {
    // Getting the reference to the class from where I want to copy over methods with their own metadata
    const routesController = FilterController;

    // The class itself consists of a prefix that must be prepended to all its member methods' metadata.
    const prefixRoute = getControllerPrefix(routesController);

    console.log("For each key (member name)")
    Reflect.ownKeys(routesController.prototype).forEach(
      (property) => {
        // Ignore the primitive class methods
        if (!['constructor', 'toString', 'length'].includes(property.toString())) {
          // Copy the methods over to the `target`
          Object.defineProperty(
            target.prototype,
            property,
            Object.getOwnPropertyDescriptor(
              routesController.prototype,
              property
            )
          )

          // Prepends class metadata `filter` to each route method's metadata
          patchRoutes(target.prototype[property], prefixRoute)

          // NOTE: An alternative to prototype property assignment (Doesn't work either)
          // target.prototype[property] = routesController.prototype[property]
          
          console.log(Reflect.getOwnMetadata(PATH_METADATA, target.prototype[property]))
        }
      })
  }
}

The patchRoutes function is like so:

const patchRoutes = <K, T extends string, P>(patchee: any, patches: (T | T[] | ((...args: P[]) => (T | T[]))), ...args: P[]) => {
  const existingPath = Reflect.getOwnMetadata(PATH_METADATA, patchee)
  if (patches instanceof Function) { patches = patches(...args) }
  if (!Array.isArray(patches)) patches = [patches]

  Reflect.defineMetadata(PATH_METADATA, (existingPath === "/" ? [...patches] : [...patches, existingPath]).join("/"), patchee)

  const createResetCallback = (resetValue, resetTarget) => () =>
    Reflect.defineMetadata(PATH_METADATA, resetValue, resetTarget)

  return createResetCallback(existingPath, patchee)
}

It returns a reset callback to reset the patched metadata.

Now, when I decorate more than one classes with this decorator, I can see a duplication of patching.

For example, patching once would give me foo/filter/... and for the second call, it'd give me bar/filter/filter/....

I wanted to see if it was the problem of copying methods over improperly, so, I tried patching the base class, copy over the patched methods and reset the metadata of the base class:

const propertyResetCb = patchRoutes(routesController.prototype[property], prefixRoute)
...
// Assigning the property now to the target
...
// Calling the reset callback
propertyResetCb()

However, this seems to reset the property of all the decorators that I've made.

This leads me to believe that it's using a singular prototype reference for the copied over methods. I'd wish to copy them free of reference (clone if you will) so that I can independently set their metadata.

Also, I'd prefer if I didn't have to modify the patchRoutes to factor in the duplication because, in the end, I'd like to do more modifications to their individual metadata separately.

Thanks :)





Aucun commentaire:

Enregistrer un commentaire