UNPKG

@flipt-io/vercel-adapter

Version:
1 lines 8.09 kB
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { FliptClient } from '@flipt-io/flipt-client-js';\n\nimport type { Adapter, FlagDefinitionsType, ProviderData } from 'flags';\n\ninterface FliptFlag {\n key: string;\n enabled: boolean;\n type: 'BOOLEAN_FLAG_TYPE' | 'VARIANT_FLAG_TYPE';\n description?: string;\n}\n\nexport interface FliptConfig {\n url?: string;\n namespace?: string;\n authentication?: {\n clientToken: string;\n };\n updateInterval?: number;\n}\n\nexport type EvaluationContext = {\n id: string;\n [key: string]: unknown;\n};\n\ntype AdapterFunction<O> = <T>(\n getValue: (obj: O) => T,\n opts?: object | undefined,\n) => Adapter<T, EvaluationContext>;\n\nexport type AdapterResponse = {\n boolean: AdapterFunction<{ enabled: boolean }>;\n variant: AdapterFunction<{ variantKey: string; attachment?: string }>;\n initialize: () => Promise<FliptClient>;\n};\n\nlet client: FliptClient | null = null;\n\nconst initialize = async (options?: FliptConfig): Promise<FliptClient> => {\n if (client) {\n await client.refresh();\n return client;\n }\n\n const config = {\n url: options?.url ?? process.env.FLIPT_URL ?? 'http://localhost:8080',\n namespace: options?.namespace ?? process.env.FLIPT_NAMESPACE ?? 'default',\n authentication: options?.authentication ?? {\n clientToken: process.env.FLIPT_CLIENT_TOKEN ?? '',\n },\n updateInterval:\n options?.updateInterval ?? (process.env.NODE_ENV === 'development' ? 5 : undefined), // 5 seconds in development if not set\n };\n\n // set global client for reuse\n client = await FliptClient.init({ ...config });\n return client;\n};\n\nconst isFliptContext = (context: unknown): context is EvaluationContext => {\n return context != null && typeof context === 'object' && 'id' in context;\n};\n\nasync function predecide(context?: EvaluationContext): Promise<EvaluationContext> {\n if (!isFliptContext(context)) {\n throw new Error(\n 'vercel-flipt-adapter: Invalid or missing context from identify. See https://flags-sdk.dev/concepts/identify',\n );\n }\n return await Promise.resolve(context);\n}\n\nexport function createFliptAdapter(options?: FliptConfig): AdapterResponse {\n async function getClient(): Promise<FliptClient> {\n return initialize(options);\n }\n\n function boolean<T>(\n getValue: (result: { enabled: boolean }) => T,\n _opts?: object | undefined,\n ): Adapter<T, EvaluationContext> {\n return {\n decide: async ({ key, entities }: { key: string; entities?: EvaluationContext }) => {\n const context = await predecide(entities);\n const client = await getClient();\n const result = client.evaluateBoolean({\n flagKey: key,\n entityId: context.id,\n context: transformContext(context),\n });\n return getValue(result);\n },\n };\n }\n\n function variant<T>(\n getValue: (result: { variantKey: string; attachment?: string }) => T,\n _opts?: object | undefined,\n ): Adapter<T, EvaluationContext> {\n return {\n decide: async ({ key, entities }: { key: string; entities?: EvaluationContext }) => {\n const context = await predecide(entities);\n const client = await getClient();\n const result = client.evaluateVariant({\n flagKey: key,\n entityId: context.id,\n context: transformContext(context),\n });\n return getValue({\n variantKey: result.variantKey,\n attachment: result.variantAttachment ?? undefined,\n });\n },\n };\n }\n\n return {\n boolean,\n variant,\n initialize: () => getClient(),\n };\n}\n\nfunction transformContext(context: EvaluationContext): Record<string, string> {\n const transformedContext: Record<string, string> = {};\n\n Object.entries(context).forEach(([key, value]) => {\n if (key !== 'id') {\n transformedContext[key] = String(value);\n }\n });\n\n return transformedContext;\n}\n\nexport const fliptAdapter = createFliptAdapter();\n\n// For testing purposes only\nexport function __resetClientForTesting(): void {\n client = null;\n}\n\nexport async function getProviderData(options?: FliptConfig): Promise<ProviderData> {\n const hints: Exclude<ProviderData['hints'], undefined> = [];\n\n if (hints.length > 0) {\n return { definitions: {}, hints };\n }\n\n try {\n const client = await initialize(options);\n const namespace = options?.namespace ?? 'default';\n const flags = client.listFlags() as FliptFlag[];\n const baseUrl = (options?.url ?? process.env.FLIPT_URL ?? 'http://localhost:8080').replace(\n /\\/+$/,\n '',\n );\n\n return {\n definitions: flags.reduce<FlagDefinitionsType>((acc, flag) => {\n const options =\n flag.type === 'BOOLEAN_FLAG_TYPE'\n ? [\n { value: true, label: 'Enabled' },\n { value: false, label: 'Disabled' },\n ]\n : []; // TODO: return variant options once they are returned by Flipt SDK\n\n acc[flag.key] = {\n description: flag.description,\n origin: `${baseUrl}/#/namespaces/${namespace}/flags/${flag.key}`,\n options,\n };\n return acc;\n }, {}),\n hints,\n };\n } catch (e) {\n return {\n definitions: {},\n hints: [\n {\n key: 'flipt/failed-to-fetch',\n text: `Failed to fetch Flipt flags: ${(e as Error).message}`,\n },\n ],\n };\n }\n}\n"],"mappings":"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","names":["client","options"]}