All files / core/src/super registry.ts

100% Statements 200/200
93.75% Branches 15/16
100% Functions 6/6
100% Lines 200/200

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 2011x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 165x 165x 24x 24x 165x 165x 165x 165x 1x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 139x 139x 139x 1x 1x 29x 29x 29x 29x 29x 29x 48x 48x 30x 30x 30x 48x 48x 48x 48x 47x 48x 29x 29x 139x 139x 139x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 96x 96x 96x 2x 2x 2x 2x 96x 96x 96x 96x 96x 1x 1x  
import { Alias } from '../typing/alias';
import { Singleton } from '../utility/singleton';
 
/**
 * String literal helper type. Enforces any assigned string to contain at least
 * three dots. **Registration**s are used by the {@link Registry} to alias
 * classes extending the base {@link Provider} as magic strings and should
 * represent sane module paths in dot-notation.
 *
 * @example
 * Library-wide **Registration** pattern:
 * ```ts
 * import { type Registration } from '@sgrud/core';
 *
 * const registration: Registration = 'sgrud.module.ClassName';
 * ```
 *
 * @see {@link Registry}
 */
export type Registration = Alias<`${string}.${string}.${string}`>;
 
/**
 * The {@link Singleton} **Registry** is a mapping used by the {@link Provider}
 * to lookup {@link Provide}d constructors by {@link Registration}s upon class
 * extension. Magic strings should represent sane module paths in dot-notation.
 * Whenever a currently not registered constructor is requested, an intermediary
 * class is created, {@link cached} internally and returned. When the actual
 * constructor is registered later, the previously created intermediary class is
 * removed from the internal caching and further steps are taken to guarantee
 * the transparent addressing of the actual constructor through the dropped
 * intermediary class.
 *
 * @decorator {@link Singleton}
 * @typeParam K - The magic string {@link Registration} type.
 * @typeParam V - The registered class constructor type.
 *
 * @see {@link Provide}
 * @see {@link Provider}
 */
@Singleton((registry, [tuples]) => {
  if (tuples) {
    for (const [key, value] of tuples) {
      registry.set(key, value);
    }
  }
 
  return registry;
})
export class Registry<
  K extends Registration,
  V extends abstract new (...args: any[]) => InstanceType<V>
> extends Map<K, V> {
 
  /**
   * Internal {@link Map}ping of all **cached**, i.e., forward-referenced, class
   * constructors. Whenever a constructor, which is not currently registered, is
   * requested as a {@link Provider}, an intermediary class is created and
   * stored within this map until the actual constructor is registered. As soon
   * as this happens, the intermediary class is removed from this map and
   * further steps are taken to guarantee the transparent addressing of the
   * actual constructor through the dropped intermediary class.
   */
  private readonly cached: Map<K, V>;
 
  /**
   * Internally used {@link WeakSet} containing all intermediary classes created
   * upon requesting a currently not registered constructor as provider. This
   * set is used internally to check if a intermediary class has already been
   * replaced by the actual constructor.
   */
  private readonly caches: WeakSet<V>;
 
  /**
   * Public **constructor**. The constructor of this class accepts the same
   * parameters as its overridden `super` {@link Map} **constructor** and acts
   * the same. I.e., through instantiating this {@link Singleton} class and
   * passing a list of `tuples` of {@link Registration}s and their corresponding
   * class constructors, these tuples may be preemptively registered.
   *
   * @param tuples - An {@link Iterable} of `tuples` provide.
   *
   * @example
   * Preemptively provide a class constructor by magic string:
   * ```ts
   * import { type Registration, Registry } from '@sgrud/core';
   * import { Service } from './service';
   *
   * const registration = 'sgrud.example.Service';
   * new Registry<Registration, typeof Service>([
   *   [registration, Service]
   * ]);
   * ```
   */
  public constructor(tuples?: Iterable<[K, V]>) {
    super();
 
    this.cached = new Map<K, V>();
    this.caches = new WeakSet<V>();
 
    if (tuples) {
      for (const [key, value] of tuples) {
        this.set(key, value);
      }
    }
  }
 
  /**
   * Overridden **get** method. Looks up the {@link Provide}d constructor by
   * magic string. If no provided constructor is found, an intermediary class is
   * created, {@link cached} internally and returned. While this intermediary
   * class and the functionality supporting it take care of inheritance, i.e.,
   * allow forward-referenced base classes to be extended, it cannot substitute
   * for the actual extended constructor. Therefore, the static extension of
   * forward-referenced classes is possible, but as long as the actual extended
   * constructor is not registered (and therefore the intermediary class still
   * {@link caches} the inheritance chain), the extending classes cannot be
   * instantiated, called etc. Doing so will result in a {@link ReferenceError}
   * being thrown.
   *
   * @param registration - The magic string to **get** the class constructor by.
   * @returns The {@link Provide}d constructor or a {@link cached} intermediary.
   * @throws A {@link ReferenceError} when a {@link cached} class is invoked.
   *
   * @example
   * Retrieve a provided constructor by magic string:
   * ```ts
   * import { type Registration, Registry } from '@sgrud/core';
   * import { type Service } from 'example-module';
   *
   * const registration = 'sgrud.example.Service';
   * new Registry<Registration, typeof Service>().get(registration);
   * ```
   */
  public override get(registration: K): V {
    let constructor = super.get(registration);
 
    if (!constructor) {
      /* c8 ignore next */
      let cache = class {} as unknown as V;
      this.cached.set(registration, cache);
      this.caches.add(cache);
 
      super.set(registration, constructor = new Proxy(cache, new Proxy({}, {
        // eslint-disable-next-line @typescript-eslint/no-shadow
        get: (_, propertyKey) => (_: unknown, ...args: any[]) => {
          if (this.caches.has(cache)) {
            if (!this.cached.has(registration)) {
              cache = super.get(registration)!;
            } else if (args[0] !== 'prototype') {
              throw new ReferenceError(registration);
            }
          }
 
          const reflection = Reflect[propertyKey as keyof typeof Reflect];
          return (reflection as Function).call(Reflect, cache, ...args);
        }
      })));
    }
 
    return constructor;
  }
 
  /**
   * Overridden **set** method. Whenever a class constructor is provided by
   * magic string through calling this method, a test is run, whether this
   * constructor was previously requested and therefore {@link cached} as
   * intermediary class. If so, the intermediary class is removed from the
   * internal mapping and further steps are taken to guarantee the transparent
   * addressing of the newly provided constructor through the previously
   * {@link cached} and now dropped intermediary class.
   *
   * @param registration - The magic string to **set** the class constructor by.
   * @param constructor - The `constructor` to register for the `registration`.
   * @returns This {@link Registry} instance.
   *
   * @example
   * Preemptively provide a constructor by magic string:
   * ```ts
   * import { type Registration, Registry } from '@sgrud/core';
   * import { Service } from './service';
   *
   * const registration = 'sgrud.example.Service';
   * new Registry<Registration, typeof Service>().set(registration, Service);
   * ```
   */
  public override set(registration: K, constructor: V): this {
    const cache = this.cached.get(registration);
 
    if (cache) {
      this.cached.delete(registration);
 
      Object.defineProperty(constructor, Symbol.hasInstance, {
        value: (instance: unknown) => instance instanceof cache
      });
    }
 
    return super.set(registration, constructor);
  }
 
}