{"version":3,"file":"Codecs.cjs","sources":["../../src/lib/Codecs.ts"],"sourcesContent":["import { match } from \"ts-pattern\";\n\nimport { CodecDecodeError } from \"./errors/CodecDecodeError\";\nimport { CodecEncodeError } from \"./errors/CodecEncodeError\";\nimport { isValidDate } from \"./helpers/common\";\n\nexport interface ArrayCodecOptions {\n  /**\n   * The delimeter character used on the `delimited` format.\n   *\n   * @default \",\"\n   */\n  delimiter?: string;\n  /**\n   * The format in which the query param array is enconded/decoded. If used on\n   * path variables, it only supports the `json` and `csv` formats.\n   *\n   * The available formats are:\n   * - **json:** `?arr=[foo,bar,baz]`\n   * - **delimited:** `?arr=foo,bar,baz` (the delimiter char can be changed)\n   * - **repeat-key:** `?arr=foo&arr=bar&arr=baz`\n   * - **key-square-brackets:** `?arr[]=foo&arr[]=bar&arr[]=baz`\n   *\n   * **Note:** An empty string (`?arr=`) means an empty array on any format.\n   *\n   * @default \"json\"\n   */\n  format?: \"json\" | \"delimited\" | \"repeat-key\" | \"key-square-brackets\";\n}\n\nexport interface DecodeQuery {\n  /**\n   * The key name of the query parameter under decode.\n   */\n  key: string;\n  /**\n   * The raw search string of the URL under decode.\n   */\n  search: string;\n}\n\nexport interface Codec<T> {\n  /**\n   * Decodes a string into a value of type `T`. The second argument is only\n   * provided when decoding query parameters.\n   *\n   * @param text the string to decode into a value\n   * @param query an object with information about the query parameters\n   * @returns the decoded value\n   */\n  decode(text: string, query?: DecodeQuery): T;\n  /**\n   * Encodes a value of type `T` into a string. The second argument is only\n   * provided when decoding query parameters.\n   *\n   * For query params, you can return either a single value or the raw seach\n   * string for the key being encoded. If the key is found in the result\n   * followed by a `=` symbol, the whole search string will be used instead of\n   * just the value.\n   *\n   * @param value the value to encode as a string\n   * @param key the key of the query parameter\n   * @returns the encoded string or search string\n   */\n  encode(value: T, key?: string): string;\n}\n\nexport interface CodecsType {\n  /**\n   * Codec for `boolean` values\n   */\n  Boolean: Codec<boolean>;\n  /**\n   * Codec for `Date` instances\n   */\n  Date: Codec<Date>;\n  /**\n   * Codec for `number` values\n   */\n  Number: Codec<number>;\n  /**\n   * Codec for `string` values\n   */\n  String: Codec<string>;\n  /**\n   * Creates a codec for any type of array. Arrays are generic so this codec\n   * requires another codec to be provided for the array inner type.\n   *\n   * @example\n   * ```\n   * Codecs.array(Codec.Number); // -> Codec<number[]>\n   * Codecs.array(Codec.String); // -> Codec<string[]>\n   * ```\n   *\n   * @param codec the codec for the array's inner type\n   * @param options an object of option to configure the codec\n   */\n  array<T>(codec: Codec<T>, options?: ArrayCodecOptions): Codec<T[]>;\n  /**\n   * Transforms the provided codec into a nullable codec. That is to say, it\n   * can now encode/decode both the provided codec type and `null`.\n   *\n   * **Note:** `null` is encoded to literally a \"null\" string. This can be\n   * ambiguious when used with the {@link Codecs.String String Codec}, so keep\n   * in mind that `null` always takes precedence when encoding/decoding a\n   * \"null\" string.\n   *\n   * @example\n   * ```\n   * Codecs.nullable(Codecs.Date); // Codec<Date | null>;\n   * ```\n   *\n   * @param codec the codec to make nullable\n   */\n   null<T>(codec: Codec<T>): Codec<T | null>;\n  /**\n   * Transforms the provided codec into a nullish codec. That is to say, it can\n   * now encode/decode both the provided codec type, `null`, and `undefined`.\n   *\n   * **Note:** `null` and `undefined` are encoded to literally \"null\" and\n   * \"undefined\" strings respectively. This can be ambiguious when used with\n   * the {@link Codecs.String String Codec}, so keep in mind that `null` and\n   * `undefined` always take precedence when encoding/decoding a \"null\" or\n   * \"undefined\" string.\n   *\n   * @example\n   * ```\n   * Codecs.nullish(Codecs.String); // Codec<string | null | undefined>;\n   * ```\n   *\n   * @param codec the codec to make nullish\n   */\n  nullish<T>(codec: Codec<T>): Codec<T | null | undefined>;\n  /**\n   * Creates a codec for a specific set of numbers as literals.\n   *\n   * @example\n   * ```\n   * type Weekday = 1 | 2 | 3 | 4 | 5 | 6 | 7;\n   *\n   * // Constraints the values to `Weekdays`\n   * Codecs.numberLiteral<Weekday>(1, 2, 3, 4, 5, 6, 7);\n   *\n   * // Infers the type from the provided values\n   * Codes.numberLiteral(2, 4, 6, 8, 10);\n   * ```\n   * @param literals the number literal for the specific codec\n   */\n  numberLiteral<T extends number>(...literals: T[]): Codec<T>;\n  /**\n   * Creates a codec for a specific set of string as literals.\n   *\n   * @example\n   * ```\n   * type Fruit = \"apple\" | \"grape\" | \"melon\";\n   *\n   * // Constraints the values to `Fruit`\n   * Codecs.stringLiteral<Fruit>(\"apple\", \"grape\", \"melon\");\n   *\n   * // Infers the type from the provided values\n   * Codecs.stringLiteral(\"Jan\", \"Feb\", \"Mar\", \"Apr\");\n   * ```\n   *\n   * @param literals the string literals for the specific codec\n   */\n  stringLiteral<T extends string>(...literals: T[]): Codec<T>;\n  /**\n   * Transforms the provided codec into an undefined codec. That is to say, it\n   * can now encode/decode both the provided codec type and `undefined`.\n   *\n   * **Note:** `undefined` is encoded to literally an \"undefined\" string. This\n   * can be ambiguious when used with the {@link Codecs.String String Codec},\n   * so keep in mind that `undefined` always takes precedence when\n   * encoding/decoding an \"undefined\" string.\n   *\n   * @example\n   * ```\n   * Codecs.undefined(Codecs.Boolean); // Codec<boolean | undefined>;\n   * ```\n   *\n   * @param codec the codec to make undefined\n   */\n  undefined<T>(codec: Codec<T>): Codec<T | undefined>;\n}\n\n/**\n * An object that provides easy access to all codecs\n */\nexport const Codecs: Readonly<CodecsType> = {\n  Boolean: {\n    decode: text => {\n      switch (text) {\n        case \"true\": return true;\n        case \"false\": return false;\n      }\n\n      throw new CodecDecodeError(`Boolean values must be \"true\" or \"false\". Got \"${text}\" instead`);\n    },\n    encode: value => {\n      if (typeof value === \"boolean\") {\n        return String(value);\n      }\n\n      throw new CodecEncodeError(`Unable to encode \"${String(value)}\". A boolean value was expected`);\n    },\n  },\n  Date: {\n    decode: text => {\n      const date = new Date(text);\n\n      if (isValidDate(date)) {\n        return date;\n      }\n\n      throw new CodecDecodeError(`Date values must have a ISO or RFC2822 format. Got \"${text}\" instead`);\n    },\n    encode: value => {\n      if (value instanceof Date && isValidDate(value)) {\n        return value.toISOString();\n      }\n\n      throw new CodecEncodeError(`Unable to encode \"${String(value)}\". A Date instance was expected`);\n    },\n  },\n  Number: {\n    decode: text => {\n      const numValue = Number(text);\n\n      if (text !== \"\" && !isNaN(numValue)) {\n        return numValue;\n      }\n\n      throw new CodecDecodeError(`Number values must be numeric only. Got \"${text}\" instead`);\n    },\n    encode: value => {\n      if (typeof value === \"number\") {\n        return String(value);\n      }\n\n      throw new CodecEncodeError(`Unable to encode \"${String(value)}\". A number value was expected`);\n    },\n  },\n  String: {\n    decode: text => text,\n    encode: value => {\n      if (typeof value === \"string\") {\n        return value;\n      }\n\n      throw new CodecEncodeError(`Unable to encode \"${String(value)}\". A string value was expected`);\n    },\n  },\n  array(codec, options = { }) {\n    const { delimiter = \",\", format = \"json\" } = options;\n    const unsupported = new CodecDecodeError(`The \"${format}\" format is only supported on query paramaters`);\n\n    return {\n      decode: (text, query) => {\n        if (text === \"\" && query === undefined) {\n          return [];\n        }\n\n        return match(format)\n          .with(\"delimited\", () => text.split(delimiter).map(value => codec.decode(value)))\n          .with(\"json\", () => {\n            if (text.startsWith(\"[\") && text.endsWith(\"]\")) {\n              const normalized = text.slice(1, text.length - 1);\n              return normalized.split(\",\").map(value => codec.decode(value));\n            }\n\n            throw new CodecDecodeError(\"Invalid array format! Expected values to be on square brackets\");\n          })\n          .with(\"key-square-brackets\", () => {\n            if (query !== undefined) {\n              const url = new URL(`http://localhost${query.search}`);\n              return url.searchParams\n                .getAll(`${query.key}[]`)\n                .map(value => codec.decode(value));\n            }\n\n            throw unsupported;\n          })\n          .with(\"repeat-key\", () => {\n            if (query !== undefined) {\n              const url = new URL(`http://localhost${query.search}`);\n              return url.searchParams\n                .getAll(query.key)\n                .map(value => codec.decode(value));\n            }\n\n            throw unsupported;\n          })\n          .exhaustive();\n      },\n      encode: (value, key) => {\n        if (Array.isArray(value)) {\n          if (value.length === 0) {\n            return \"\";\n          }\n\n          const array = value.map(val => codec.encode(val));\n\n          return match(format)\n            .with(\"delimited\", () => array.join(delimiter))\n            .with(\"json\", () => `[${array.join(\",\")}]`)\n            .with(\"key-square-brackets\", () => array.map(val => `${key}[]=${val}`).join(\"&\"))\n            .with(\"repeat-key\", () => array.map(val => `${key}=${val}`).join(\"&\"))\n            .exhaustive();\n        }\n\n        throw new CodecEncodeError(`Unable to encode \"${String(value)}\". An array value was expected`);\n      },\n    };\n  },\n  null(codec) {\n    return {\n      decode: text => text === \"null\" ? null : codec.decode(text),\n      encode: value => value === null ? \"null\" : codec.encode(value),\n    };\n  },\n  nullish(codec) {\n    return this.null(this.undefined(codec));\n  },\n  numberLiteral(...literals) {\n    return {\n      decode: text => {\n        const decoded = this.Number.decode(text);\n        const isLiteral = (num: number): num is typeof literals[number] => {\n          return literals.map(Number).includes(num);\n        };\n\n        if (isLiteral(decoded)) {\n          return decoded;\n        }\n\n        throw new CodecDecodeError(`Literal value must be one of \"[${literals.join(\", \")}]\". Got \"${text}\" instead`);\n      },\n      encode: value => {\n        if (literals.includes(value)) {\n          return this.Number.encode(value);\n        }\n\n        throw new CodecEncodeError(\n          `Unable to encode \"${value}\". A literal value of \"[${literals.join(\", \")}]\" was expected`,\n        );\n      },\n    };\n  },\n  stringLiteral(...literals) {\n    return {\n      decode: text => {\n        const isLiteral = (str: string): str is typeof literals[number] => {\n          return literals.map(String).includes(str);\n        };\n\n        if (isLiteral(text)) {\n          return text;\n        }\n\n        throw new CodecDecodeError(`Literal value must be one of \"[${literals.join(\", \")}]\". Got \"${text}\" instead`);\n      },\n      encode: value => {\n        if (literals.includes(value)) {\n          return value;\n        }\n\n        throw new CodecEncodeError(\n          `Unable to encode \"${value}\". A literal value of \"[${literals.join(\", \")}]\" was expected`,\n        );\n      },\n    };\n  },\n  undefined(codec) {\n    return {\n      decode: text => text === \"undefined\" ? undefined : codec.decode(text),\n      encode: value => value === undefined ? \"undefined\" : codec.encode(value),\n    };\n  },\n};\n\n/**\n * Extends the {@link Codecs} object to and custom codecs to it. This is just a\n * convenience to have all codecs in one place and avoid rexporting an extended\n * Codecs object.\n *\n * @param name the name of the new codec\n * @param codec a codec or a function that returns a codec\n */\nexport function addCodec<T>(name: string, codec: Codec<T> | ((...args: any[]) => Codec<T>)): void {\n  Object.defineProperty(Codecs, name, { value: codec });\n}\n"],"names":["CodecDecodeError","CodecEncodeError","isValidDate","match"],"mappings":"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"}