UNPKG

flags

Version:

Flags SDK by Vercel - The feature flags toolkit for Next.js and SvelteKit

1 lines 12.7 kB
{"version":3,"sources":["/home/runner/work/flags/flags/packages/flags/dist/chunk-WECCP5XG.cjs","../src/lib/normalize-options.ts","../src/lib/async-memoize-one.ts","../src/lib/serialization.ts"],"names":[],"mappings":"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","file":"/home/runner/work/flags/flags/packages/flags/dist/chunk-WECCP5XG.cjs","sourcesContent":[null,"import type { FlagOption, GenerousOption } from '../types';\n\nexport function normalizeOptions<T>(\n flagOptions: GenerousOption<T>[] | undefined,\n): FlagOption<T>[] | undefined {\n if (!Array.isArray(flagOptions)) return flagOptions;\n\n return flagOptions.map((option) => {\n if (typeof option === 'boolean') return { value: option };\n if (typeof option === 'number') return { value: option };\n if (typeof option === 'string') return { value: option };\n if (option === null) return { value: option };\n\n return option;\n }) as FlagOption<T>[];\n}\n","// adapted from https://github.com/microlinkhq/async-memoize-one\n// and https://github.com/alexreardon/memoize-one\n\ntype MemoizeOneOptions = {\n cachePromiseRejection?: boolean;\n};\n\ntype MemoizedFn<TFunc extends (this: any, ...args: any[]) => any> = (\n this: ThisParameterType<TFunc>,\n ...args: Parameters<TFunc>\n) => ReturnType<TFunc>;\n\n/**\n * Memoizes an async function, but only keeps the latest result\n */\nexport function memoizeOne<TFunc extends (this: any, ...newArgs: any[]) => any>(\n fn: TFunc,\n isEqual: (a: Parameters<TFunc>, b: Parameters<TFunc>) => boolean,\n { cachePromiseRejection = false }: MemoizeOneOptions = {},\n): MemoizedFn<TFunc> {\n let calledOnce = false;\n let oldArgs: Parameters<TFunc>;\n let lastResult: any;\n\n function memoized(\n this: ThisParameterType<TFunc>,\n ...newArgs: Parameters<TFunc>\n ) {\n if (calledOnce && isEqual(newArgs, oldArgs)) return lastResult;\n\n lastResult = fn.apply(this, newArgs);\n\n if (!cachePromiseRejection && lastResult.catch) {\n lastResult.catch(() => {\n calledOnce = false;\n });\n }\n\n calledOnce = true;\n oldArgs = newArgs;\n\n return lastResult;\n }\n\n return memoized;\n}\n","import { base64url, CompactSign, compactVerify } from 'jose';\nimport type { JsonValue } from '..';\nimport type { FlagOption } from '../types';\nimport { memoizeOne } from './async-memoize-one';\n\n// 252 max options length allows storing index 0 to 251,\n// so 252 is the first SPECIAL_INTEGER\nexport const MAX_OPTION_LENGTH = 252;\n\nenum SPECIAL_INTEGERS {\n /** Signals that the returned value is not listed in the flag's options */\n NULL = 252,\n BOOLEAN_FALSE = 253,\n BOOLEAN_TRUE = 254,\n UNLISTED_VALUE = 255,\n}\n\nconst memoizedVerify = memoizeOne(\n (code: string, secret: string) =>\n compactVerify(code, base64url.decode(secret), {\n algorithms: ['HS256'],\n }),\n (a, b) => a[0] === b[0] && a[1] === b[1], // only first two args matter\n { cachePromiseRejection: true },\n);\n\nconst memoizedSign = memoizeOne(\n (uint8Array: Uint8Array, secret) =>\n new CompactSign(uint8Array)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(base64url.decode(secret)),\n (a, b) =>\n // matchedIndices array must be equal\n a[0].length === b[0].length &&\n a[0].every((v, i) => b[0][i] === v) &&\n // secrets must be equal\n a[1] === b[1],\n { cachePromiseRejection: true },\n);\n\nfunction splitUint8Array(\n array: Uint8Array,\n index: number,\n): [Uint8Array, Uint8Array] {\n const firstHalf = array.slice(0, index);\n const secondHalf = array.slice(index);\n return [firstHalf, secondHalf];\n}\n\n/**\n * Common subset of the flag type used in here\n */\ntype Flag = {\n key: string;\n options?: FlagOption<any>[];\n};\n\nexport async function deserialize(\n code: string,\n flags: readonly Flag[],\n secret: string,\n): Promise<Record<string, JsonValue>> {\n // TODO what happens when verification fails?\n const { payload } = await memoizedVerify(code, secret);\n\n const [matchedIndicesArray, valuesUint8Array] =\n payload.length === flags.length\n ? [payload]\n : splitUint8Array(payload, flags.length);\n\n const valuesArray = valuesUint8Array\n ? // re-add opening and closing brackets since we remove them when serializing\n JSON.parse(`[${new TextDecoder().decode(valuesUint8Array)}]`)\n : null;\n\n let spilled = 0;\n return matchedIndicesArray.reduce<Record<string, JsonValue>>(\n (acc, valueIndex, index) => {\n const flag = flags[index];\n\n if (!flag) {\n throw new Error(`flags: No flag at index ${index}`);\n }\n\n switch (valueIndex) {\n case SPECIAL_INTEGERS.BOOLEAN_FALSE:\n acc[flag.key] = false;\n break;\n case SPECIAL_INTEGERS.BOOLEAN_TRUE:\n acc[flag.key] = true;\n break;\n case SPECIAL_INTEGERS.UNLISTED_VALUE:\n acc[flag.key] = valuesArray[spilled++];\n break;\n case SPECIAL_INTEGERS.NULL:\n acc[flag.key] = null;\n break;\n default:\n acc[flag.key] = flag.options?.[valueIndex]?.value as JsonValue;\n }\n\n return acc;\n },\n {},\n );\n}\n\n/**\n * When serializing flags we find the matching option index for each evaluated value.\n *\n * This means we potentially need to iterate through all options of all flags.\n *\n * When the value we're trying to match is a literal (bool, string, number)\n * we look for it using referntial equality.\n *\n * When the value is an array or object we stringify the value and we stringify\n * the options of each flag and then we search for it by string comparison.\n *\n * This is faster than doing a deep equality check and also allows us not to\n * use any external library.\n *\n * We also cache the result of stringifying all options so in a Map so we only\n * ever need to stringify them once.\n */\nconst matchIndex = (() => {\n const stringifiedOptionsCache = new Map<FlagOption<any>[], string[]>();\n return function matchIndex(options: FlagOption<any>[], value: JsonValue) {\n const t = typeof value;\n\n // we're looking for a literal value, so we can check using referntial equality\n if (value === null || t === 'boolean' || t === 'string' || t === 'number') {\n return options.findIndex((v) => v.value === value);\n }\n\n // we're looking for an array or object, so we should check stringified\n const stringifiedValue = JSON.stringify(value);\n let stringifiedOptions = stringifiedOptionsCache.get(options);\n if (!stringifiedOptions) {\n stringifiedOptions = options.map((o) => JSON.stringify(o.value));\n stringifiedOptionsCache.set(options, stringifiedOptions);\n }\n\n return stringifiedOptions.indexOf(stringifiedValue);\n };\n})();\n\nfunction joinUint8Arrays(array1: Uint8Array, array2: Uint8Array): Uint8Array {\n const combined = new Uint8Array(array1.length + array2.length);\n combined.set(array1);\n combined.set(array2, array1.length);\n return combined;\n}\nexport async function serialize(\n flagSet: Record<Flag['key'], JsonValue>,\n flags: readonly Flag[],\n secret: string,\n) {\n const unlistedValues: JsonValue[] = [];\n\n const matchedIndices = new Uint8Array(\n flags.map((flag) => {\n const options = Array.isArray(flag.options) ? flag.options : [];\n\n const value = flagSet[flag.key];\n if (!Object.hasOwn(flagSet, flag.key) || value === undefined) {\n throw new Error(`flags: Missing value for flag \"${flag.key}\"`);\n }\n\n // avoid searching for common values\n // and ensure they can always be compressed, even if not listed in options\n switch (value) {\n case null:\n return SPECIAL_INTEGERS.NULL;\n case false:\n return SPECIAL_INTEGERS.BOOLEAN_FALSE;\n case true:\n return SPECIAL_INTEGERS.BOOLEAN_TRUE;\n }\n\n const matchedIndex = matchIndex(options, value);\n if (matchedIndex > -1) return matchedIndex;\n\n // value was not listed in options, so we need to\n // transport it using JSON.stringify(). we return 255 to\n // indicate this value is stringified.\n // stringified values will be placed at the end of the\n // indices array\n unlistedValues.push(value);\n return SPECIAL_INTEGERS.UNLISTED_VALUE;\n }),\n );\n\n let joined: Uint8Array;\n // there were unlisted values, so we need to join arrays\n if (unlistedValues.length > 0) {\n const jsonArray = new TextEncoder().encode(\n // slicing removes opening and closing array brackets as they'll always be\n // there and we can re-add them when deserializing\n JSON.stringify(unlistedValues).slice(1, -1),\n );\n joined = joinUint8Arrays(matchedIndices, jsonArray);\n } else {\n joined = matchedIndices;\n }\n\n return memoizedSign(joined, secret);\n}\n"]}