All files / data/src/relation property.ts

100% Statements 102/102
100% Branches 16/16
100% Functions 3/3
100% Lines 102/102

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 1031x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 17x 17x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 264x 257x 264x 34x 538x 538x 518x 518x 109x 109x 538x 538x 34x 34x 34x 34x 34x  
import { assign, Mutable, TypeOf } from '@sgrud/core';
import { Model } from '../model/model';
 
/**
 * Type alias for a union type of all primitive constructors which may be used
 * as `typeFactory` argument for the {@link Property} decorator.
 *
 * @see {@link Property}
 */
export type Property =
  typeof Boolean |
  typeof Date |
  typeof Number |
  typeof Object |
  typeof String;
 
/**
 * Unique symbol used as property key by the {@link Property} decorator to
 * register decorated {@link Model} fields for further computation, e.g.,
 * serialization, treemapping etc.
 *
 * @see {@link Property}
 */
export const property = Symbol('@sgrud/data/model/property');
 
/**
 * {@link Model} field decorator factory. Using this decorator, {@link Model}s
 * can be enriched with primitive fields. The compatible primitives are the
 * subset of primitives JavaScript shares with JSON, i.e., {@link Boolean},
 * {@link Date} (serialized), {@link Number} and {@link String}. {@link Object}s
 * cannot be uses as a `typeFactory` argument value, as {@link Model} fields
 * containing objects should be declared by the {@link HasOne} and
 * {@link HasMany} decorators. By employing this decorator, the decorated field
 * will (depending on the `transient` argument value) be taken into account when
 * serializing or treemapping the {@link Model} containing the decorated field.
 *
 * @param typeFactory - A forward reference to the field value constructor.
 * @param transient - Whether the decorated field is `transient`.
 * @typeParam T - The field value constructor type.
 * @returns A {@link Model} field decorator.
 *
 * @example
 * {@link Model} with a primitive field:
 * ```ts
 * import { Model, Property } from '@sgrud/data';
 *
 * export class ExampleModel extends Model<ExampleModel> {
 *
 *   ⁠@Property(() => String)
 *   public field?: string;
 *
 *   protected [Symbol.toStringTag]: string = 'ExampleModel';
 *
 * }
 * ```
 *
 * @see {@link Model}
 * @see {@link HasOne}
 * @see {@link HasMany}
 */
export function Property<T extends Property>(
  typeFactory: () => T,
  transient: boolean = false
) {
 
  /**
   * @param model - The {@link Model} to be decorated.
   * @param field - The {@link Model} `field` to be decorated.
   */
  return function<M extends Model>(model: M, field: Model.Field<M>): void {
    const key = '#' + field as Model.Field<M>;
 
    if (!transient) {
      assign((model as Mutable<M>)[property] ||= {}, {
        [field]: typeFactory
      });
    }
 
    Object.defineProperties(model, {
      [key]: {
        writable: true
      },
      [field]: {
        enumerable: true,
        get(this: M): InstanceType<T> | null | undefined {
          if (TypeOf.null(this[key])) return null;
          else return this[key]?.valueOf() as InstanceType<T>;
        },
        set(this: M, value?: InstanceType<T>): void {
          if (TypeOf.null(value)) {
            (this[key] as unknown) = null;
          } else if (!TypeOf.undefined(value)) {
            (this[key] as unknown) = new (typeFactory())(value);
          } else return;
 
          this.changes.next(this);
        }
      }
    });
  };
 
}