File size: 974 Bytes
05c5ed5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/**
 * @module tagged-types
 * Minimal runtime type checking with discriminated unions
 */

const DEFAULT_KEY = "__$ref__" as const;

export type Tagged<TTag extends string, TData> = TData & {
  [DEFAULT_KEY]: TTag;
};

class TagBuilder<TData, TTag extends string> {
  constructor(private tagValue: TTag) {}

  isMaybe = (value: unknown): value is Tagged<TTag, TData> => {
    return (
      value !== null &&
      value !== undefined &&
      typeof value === "object" &&
      DEFAULT_KEY in value &&
      (value as any)[DEFAULT_KEY] === this.tagValue
    );
  };

  create = (data: TData): Tagged<TTag, TData> => {
    return {
      ...data,
      [DEFAULT_KEY]: this.tagValue,
    } as Tagged<TTag, TData>;
  };
  unwrap = (value: Tagged<TTag, TData>): TData => {
    const { [DEFAULT_KEY]: _, ...data } = value;
    return data as TData;
  };
}

export function tag<TData>(tagName: string) {
  return Object.freeze(new TagBuilder<TData, typeof tagName>(tagName));
}