npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@proc7ts/amend

v1.0.1

Published

Programmatically reusable decorators (amendments) for TypeScript

Downloads

13

Readme

Amendments

Programmatically reusable decorators for TypeScript

NPM Build Status Code Quality Coverage GitHub Project API Documentation

Class Member Amendments

import { AeMember } from '@proc7ts/amend';

class MyClass {
  @AeMember(({ key, get, set, amend }) =>
    amend({
      get(instance) {
        // Replace the getter.

        const value = get(instance); // Read the value with default getter.

        console.debug(`${key} value read:`, value);

        return value;
      },
      set(instance, update) {
        // Replace the setter.

        const oldValue = get(instance);

        set(instance, update);

        console.debug(`${key} value updated:`, oldValue, ' -> ', update);
      },
    }),
  )
  field = 'value';
}

Here @AeMember() creates an amendment that can be used as a class member decorator. This decorator can be applied to property, accessor, or method. In any case the provided get and set functions read and write values correspondingly.

The @AeMember() accepts arbitrary number of nested amendments. Each amendment receives an object with the following properties (from AeMember and AmendTarget.Core interfaces):

  • amendedClass - Amended class constructor.
  • key - Amended member key.
  • configurable - Whether the amended member is configurable.
  • enumberable - Whether the amended member is enumerable.
  • readable - Whether the amended member is readable. The member is readable, unless it has only setter.
  • writable - Whether the amended member is writable. The member is writable, unless it has only setter, or it is defined non-writable.
  • get(instance) - Member value reader function.
  • set(instance, update) - Member value writer function.
  • amend(request) - Member amendment function.

The amend() function call modifies the member definition. It accepts an object with the same properties and overrides the member definition:

  • If get or set specified and differ from the passed in value, then the member converted to accessor with corresponding get and/or set operations.

    If get omitted, then the member becomes non-readable.

    If set omitted, then the member becomes non-writable.

    Note that get and set operations passed in still could be used even from inside their replacements. They would act as before.

  • If neither get, nor set specified, then the member value access operations remain unchanged.

  • If configurable or enumerable specified and differ from the values passes in, then these properties used to update the property descriptor.

  • The rest of the properties are ignored.

Static Member Amendments

The @AeStatic() creates an amendment that can be used as a static class member decorator. It is equivalent to @AeMember(), except it is applicable to static members.

Class Amendments

The @AeClass() creates an amendment that can be used as a class decorator.

The nested amendments receive an object with only amendedClass and amend() properties.

Custom Amendments

Custom amendment can be created by function that calls one of the predefined ones. It can be declared like this:

import { AeMember, AmendTarget, MemberAmendment } from '@proc7ts/amend';
import { Class } from '@proc7ts/primitives';

export function LoggedMember<
  TValue extends TUpdate, // Member value type.
  TClass extends Class = Class, // Amended class type.
  TUpdate = TValue, // Member value update type accepted by its setter.
  TAmended extends AeMember<TValue, TClass, TAmended> = AeMember<TValue, TClass, TAmended>, // Amended entity type.
>(): MemberAmendment<TValue, TClass, TUpdate, TAmended> {
  return AeMember(
    (
      { key, get, set, amend }: AmendTarget<AeMember<TValue, TClass, TUpdate>>, // Amendment target. Contains amended entity properties
    ) =>
      // along with `amend()` function.
      amend({
        get(instance) {
          // Replace the getter.

          const value = get(instance); // Read the value with default getter.

          console.debug(`${key} value read:`, value);

          return value;
        },
        set(instance, update) {
          // Replace the setter.

          const oldValue = get(instance);

          set(instance, update);

          console.debug(`${key} value updated:`, oldValue, ' -> ', update);
        },
      }),
  );
}

Then the first example could be rewritten like this:

class MyClass {
  @LoggedMember()
  field = 'value';
}

Combining Amendments

The simplest way to combine multiple amendments is to apply multiple decorators.

However, it is possible to declare a combined amendment that applies multiple amendments by single decorator:

import { AeMember, AmendTarget, MemberAmendment } from '@proc7ts/amend';
import { Class } from '@proc7ts/primitives';

/**
 * Logs member reads.
 */
export function ReadLoggedMember<
  TValue extends TUpdate, // Member value type.
  TClass extends Class = Class, // Amended class type.
  TUpdate = TValue, // Member value update type accepted by its setter.
  TAmended extends AeMember<TValue, TClass, TAmended> = AeMember<TValue, TClass, TAmended>, // Amended entity type.
>(): MemberAmendment<TValue, TClass, TUpdate, TAmended> {
  return AeMember(({ key, get, set, amend }: AmendTarget<AeMember<TValue, TClass, TUpdate>>) =>
    amend({
      get(instance) {
        // Replace the getter.

        const value = get(instance); // Read the value with default getter.

        console.debug(`${key} value read:`, value);

        return value;
      },
      set, // The setter remains unchanged.
    }),
  );
}

/**
 * Logs member writes.
 */
export function WriteLoggedMember<
  TValue extends TUpdate, // Member value type.
  TClass extends Class = Class, // Amended class type.
  TUpdate = TValue, // Member value update type accepted by its setter.
  TAmended extends AeMember<TValue, TClass, TAmended> = AeMember<TValue, TClass, TAmended>, // Amended entity type.
>(): MemberAmendment<TValue, TClass, TUpdate, TAmended> {
  return AeMember(({ key, get, set, amend }: AmendTarget<AeMember<TValue, TClass, TUpdate>>) =>
    amend({
      get, // The getter remains unchanged.
      set(instance, update) {
        // Replace the setter.

        const oldValue = get(instance);

        set(instance, update);

        console.debug(`${key} value updated:`, oldValue, ' -> ', update);
      },
    }),
  );
}

/**
 * Logs any member access.
 */
export function LoggedMember<
  TValue extends TUpdate, // Member value type.
  TClass extends Class = Class, // Amended class type.
  TUpdate = TValue, // Member value update type accepted by its setter.
  TAmended extends AeMember<TValue, TClass, TAmended> = AeMember<TValue, TClass, TAmended>, // Amended entity type.
>(): MemberAmendment<TValue, TClass, TUpdate, TAmended> {
  // Apply both amendments in chain.
  return AeMember(ReadLoggedMember(), WriteLoggedMember());
}

Other Helpful Amendments

The library contains a few more helpful amendments:

  • @AeMembers() - A class amendment that amends existing and declares new class members.
  • @AeStatics() - A class amendment that amends existing and declares new static members.
  • @PseudoMember() - A class amendment that declares a pseudo-member, which is not actually defined in class prototype. Such member value may be derived from the real one.
  • @PseudoStatic() - A class amendment that declares a static pseudo-member, which is not actually defined in class constructor.

See the API documentation for the detailed info.

Auto-Amendment

There are two issues with TypeScript decorators:

  1. They are experimental. They may change in future releases, and probably will due to ECMAScript chosen another approach.

  2. Each TypeScript decorator adds a __decorate() function call to generated JavaScript. This function has side effects, so the bundler is unable to tree-shake it. The latter could be a major issue (especially for the library authors), as a bundler would add all decorated classes to application bundle, even unused ones.

Auto-amendment designed to resolve these issues.

To make it work just extend an Amendable abstract class, and place the amendments to autoAmend static method:

import { AeClassTarget, AeMembers, Amendable } from '@proc7ts/amend';

class MyClass extends Amendable {
  static autoAmend(target: AeClassTarget<typeof MyClass>): void {
    // Apply amendments here.
    AeMembers({
      field: LoggedMember(), // An amendment of `field` property.
    }).applyAmendment(target);
  }

  field = 'value';
}

Auto-amendment will be applied to the class when the first instance of that class constructed.

Alternatively, the class could be amended explicitly by calling an amend() function with that class as an argument. The class (and its super-classes) would be auto-amended at most once. The amend() method could be safely called multiple times for the same class.

It is not necessary to extend an Amendable class if amend() will be called explicitly for the class. It would be enough to implement an autoAmend static method.

An explicit amendment with amend() function call could be necessary, e.g. when accessing amended static members.