/ packages / schema / utils.ts
utils.ts
  1  import type { CodecValue } from "@massmarket/utils/codec";
  2  export class BaseClass {
  3    static isBaseClass(value: unknown): value is BaseClass {
  4      return (
  5        value instanceof BaseClass ||
  6        (value !== null && typeof value === "object" && "asCBORMap" in value &&
  7          typeof value.asCBORMap === "function")
  8      );
  9    }
 10  
 11    asCBORMap(): CodecValue {
 12      const map = new Map();
 13      for (const [key, value] of Object.entries(this)) {
 14        if (
 15          ["string", "number", "boolean", "bigint"].includes(typeof value) ||
 16          value instanceof Uint8Array
 17        ) {
 18          map.set(key, value);
 19        } else if (value instanceof Date) {
 20          map.set(key, value);
 21        } else if (Array.isArray(value)) {
 22          const newArray = [];
 23          for (const v of value) {
 24            if (BaseClass.isBaseClass(v)) {
 25              newArray.push(v.asCBORMap());
 26            } else {
 27              newArray.push(v);
 28            }
 29          }
 30          map.set(key, newArray);
 31        } else if (value instanceof Map) {
 32          const newMap = new Map();
 33          for (const [k, v] of value.entries()) {
 34            if (BaseClass.isBaseClass(v)) {
 35              newMap.set(k, v.asCBORMap());
 36            } else {
 37              newMap.set(k, v);
 38            }
 39          }
 40          map.set(key, newMap);
 41        } else if (BaseClass.isBaseClass(value)) {
 42          map.set(key, value.asCBORMap());
 43        } else if (value === undefined) {
 44          // ignore
 45        } else {
 46          throw new Error(
 47            `Unknown value on ${key}. Type: ${typeof value}`,
 48          );
 49        }
 50      }
 51      return map;
 52    }
 53  }
 54  
 55  // Helper functions for type validation
 56  export function ensureNumber(value: unknown, fieldName: string): number {
 57    if (typeof value !== "number" || isNaN(value)) {
 58      throw new TypeError(
 59        `Expected ${fieldName} to be a number, got ${typeof value}`,
 60      );
 61    }
 62    return value;
 63  }
 64  
 65  // Helper functions for type validation
 66  export function ensureBigInt(value: unknown, fieldName: string): bigint {
 67    if (typeof value !== "bigint") {
 68      throw new TypeError(
 69        `Expected ${fieldName} to be a bigint, got ${typeof value}`,
 70      );
 71    }
 72    return value;
 73  }
 74  
 75  // Helper returns number or bigint
 76  export function ensureSomeNumberAsBigInt(
 77    value: unknown,
 78    fieldName: string,
 79  ): bigint | number {
 80    if (typeof value === "bigint") {
 81      return value;
 82    }
 83    const num = ensureNumber(value, fieldName);
 84    // TODO: i'd prefer this but when going to asCBORMap() it needs to convert to smallest possible value, which is annoying..
 85    // return BigInt(num);
 86    return num;
 87  }
 88  
 89  export function ensureString(value: unknown, fieldName: string): string {
 90    if (typeof value !== "string") {
 91      throw new TypeError(
 92        `Expected ${fieldName} to be a string, got ${typeof value}`,
 93      );
 94    }
 95    return value;
 96  }
 97  
 98  export function ensureBoolean(value: unknown, fieldName: string): boolean {
 99    if (typeof value !== "boolean") {
100      throw new TypeError(
101        `Expected ${fieldName} to be a boolean, got ${typeof value}`,
102      );
103    }
104    return value;
105  }
106  
107  export function ensureStringArray(value: unknown, fieldName: string): string[] {
108    if (
109      !Array.isArray(value) || !value.every((item) => typeof item === "string")
110    ) {
111      throw new TypeError(`Expected ${fieldName} to be an array of strings`);
112    }
113    return value;
114  }
115  
116  export function ensureMap<K, V>(value: unknown, fieldName: string): Map<K, V> {
117    if (!(value instanceof Map)) {
118      throw new TypeError(
119        `Expected ${fieldName} to be a Map, got ${typeof value}`,
120      );
121    }
122    return value as Map<K, V>;
123  }
124  
125  // export function ensureMap(value: unknown, fieldName: string): Map<string, unknown> {
126  //   if (!(value instanceof Map)) {
127  //     throw new TypeError(`Expected ${fieldName} to be a Map, got ${typeof value}`);
128  //   }
129  //   return value;
130  // }
131  
132  export function ensureUint8Array(
133    value: unknown,
134    fieldName: string,
135    length?: number,
136  ): Uint8Array {
137    if (!(value instanceof Uint8Array)) {
138      throw new TypeError(
139        `Expected ${fieldName} to be a Uint8Array, got ${typeof value}`,
140      );
141    }
142    if (length !== undefined && value.length !== length) {
143      throw new TypeError(
144        `Expected ${fieldName} to be a Uint8Array of length ${length}, got ${value.length}`,
145      );
146    }
147    return value;
148  }
149  
150  export function ensureDate(value: unknown, fieldName: string): Date {
151    if (!(value instanceof Date)) {
152      throw new TypeError(
153        `Expected ${fieldName} to be a Date, got ${typeof value}`,
154      );
155    }
156    return value;
157  }