@variantlab/next 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/app-router.cjs +325 -0
- package/dist/app-router.cjs.map +1 -0
- package/dist/app-router.d.cts +41 -0
- package/dist/app-router.d.ts +41 -0
- package/dist/app-router.js +309 -0
- package/dist/app-router.js.map +1 -0
- package/dist/client/hooks.cjs +191 -0
- package/dist/client/hooks.cjs.map +1 -0
- package/dist/client/hooks.d.cts +62 -0
- package/dist/client/hooks.d.ts +62 -0
- package/dist/client/hooks.js +178 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/index.cjs +319 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +318 -0
- package/dist/index.d.ts +318 -0
- package/dist/index.js +304 -0
- package/dist/index.js.map +1 -0
- package/dist/pages-router.cjs +325 -0
- package/dist/pages-router.cjs.map +1 -0
- package/dist/pages-router.d.cts +26 -0
- package/dist/pages-router.d.ts +26 -0
- package/dist/pages-router.js +309 -0
- package/dist/pages-router.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/server/cookie.ts","../src/server/create-variant-lab-server.ts","../src/server/get-variant-ssr.ts","../src/server/middleware.ts","../src/index.ts"],"names":["validateConfig","createEngine"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Shared types for `@variantlab/next`.\n *\n * Kept in a single tiny file so every entrypoint (server barrel,\n * app-router subpath, pages-router subpath, client provider) can\n * import without pulling in code.\n */\n\nimport type { ExperimentsConfig, VariantContext } from \"@variantlab/core\";\nimport type { ReactNode } from \"react\";\n\n/**\n * On-wire cookie payload shape. Short keys to minimize cookie bytes.\n *\n * v — schema version (currently 1)\n * u — userId (required; generated on first visit by middleware)\n * a — assignments: { [experimentId]: variantId } (may be empty)\n */\nexport interface StickyCookiePayload {\n readonly v: 1;\n readonly u: string;\n readonly a: Readonly<Record<string, string>>;\n}\n\n/**\n * Anything from which we can read an HTTP Cookie header. Supports:\n *\n * - Fetch-style `Request` (App Router Route Handlers, middleware)\n * - `NextApiRequest` / Pages Router `req` (has `.cookies` and `.headers`)\n * - Next 14+ `ReadonlyRequestCookies` from `cookies()` in `next/headers`\n * - A plain cookie header string\n */\nexport type CookieSource =\n | Request\n | RequestCookieJar\n | PagesRouterRequestLike\n | string\n | null\n | undefined;\n\n/**\n * Shape we rely on from Next's `cookies()` return value. Kept minimal\n * so we don't depend on `next/headers` types at compile time — the\n * server barrel must be framework-agnostic enough to tree-shake.\n */\nexport interface RequestCookieJar {\n get(name: string): { readonly value: string } | undefined;\n}\n\n/**\n * Minimal Pages Router / `NextApiRequest` shape. Avoids importing\n * `next` types in the base entrypoint.\n */\nexport interface PagesRouterRequestLike {\n readonly cookies?: Readonly<Record<string, string | undefined>>;\n readonly headers?: Readonly<Record<string, string | string[] | undefined>>;\n}\n\n/**\n * Options accepted by `createVariantLabServer` and the SSR helpers.\n */\nexport interface VariantLabServerOptions {\n /** Cookie name. Defaults to `__variantlab_sticky`. */\n readonly cookieName?: string;\n /** Max age in seconds. Defaults to 365 days. */\n readonly maxAge?: number;\n /** Cookie path. Defaults to `/`. */\n readonly path?: string;\n /** `SameSite` attribute. Defaults to `\"lax\"`. */\n readonly sameSite?: \"strict\" | \"lax\" | \"none\";\n /** Override for the `Secure` flag. When `undefined`, derived from the request URL. */\n readonly secure?: boolean;\n /** `HttpOnly` flag. Defaults to `true`. */\n readonly httpOnly?: boolean;\n /** `Domain` attribute. Defaults to undefined (host-only cookie). */\n readonly domain?: string;\n}\n\n/**\n * Props accepted by the Next `VariantLabProvider` Client Component.\n */\nexport interface VariantLabProviderProps {\n /** Validated `ExperimentsConfig` or a raw JSON module import. */\n readonly config: unknown | ExperimentsConfig;\n /** Runtime context (userId, locale, platform, …) applied before first render. */\n readonly initialContext?: VariantContext;\n /**\n * Assignments computed on the server. Seeded into the engine cache so\n * the first `getVariant` call on the client returns the same variant\n * that was server-rendered, without re-evaluating targeting.\n */\n readonly initialVariants?: Readonly<Record<string, string>>;\n readonly children?: ReactNode;\n}\n\n/** Default cookie name. Shared between server helpers and middleware. */\nexport const DEFAULT_COOKIE_NAME = \"__variantlab_sticky\";\n\n/** Default max age (365 days, in seconds). */\nexport const DEFAULT_MAX_AGE = 60 * 60 * 24 * 365;\n","/**\n * Hand-rolled cookie codec + parser for `@variantlab/next`.\n *\n * Goals:\n * 1. Zero runtime dependencies. No `cookie` package, no `js-base64`.\n * 2. Edge-runtime safe. Uses only `TextEncoder`/`TextDecoder`,\n * `globalThis.crypto`, standard `atob`/`btoa`, and `Object.create(null)`\n * — all available on Vercel Edge, Cloudflare Workers, Deno, Bun, Node 18+.\n * 3. Prototype-pollution hardened. Mirrors the guards in\n * `packages/core/src/config/validator.ts`: cookie parser rejects\n * `__proto__`, `constructor`, `prototype` as cookie names, and the\n * JSON payload is parsed through `Object.create(null)` sanitization.\n * 4. Size-capped. A malicious client sending a 10 MB Cookie header gets\n * rejected before allocation by `MAX_COOKIE_HEADER_BYTES`.\n */\n\nimport type {\n CookieSource,\n PagesRouterRequestLike,\n RequestCookieJar,\n StickyCookiePayload,\n VariantLabServerOptions,\n} from \"../types.js\";\nimport { DEFAULT_COOKIE_NAME, DEFAULT_MAX_AGE } from \"../types.js\";\n\n/** Hard cap on cookie header length (4 KB × 2 for long headers). */\nconst MAX_COOKIE_HEADER_BYTES = 8192;\n/** Hard cap on a single decoded payload. */\nconst MAX_PAYLOAD_BYTES = 4096;\n/** Cookie names that are never read (prototype-pollution guard). */\nconst RESERVED_NAMES = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n// ---------------------------------------------------------------------------\n// base64url codec (hand-rolled, 16 LOC)\n// ---------------------------------------------------------------------------\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\nfunction base64urlEncode(input: string): string {\n const bytes = textEncoder.encode(input);\n // `btoa` takes a latin-1 string, so map bytes → chars first.\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i] as number);\n }\n const b64 = btoa(binary);\n return b64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nfunction base64urlDecode(input: string): string | null {\n // Reject anything that isn't legal base64url up front.\n if (!/^[A-Za-z0-9\\-_]*$/.test(input)) return null;\n const pad = input.length % 4;\n const padded = input.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"====\".slice(pad === 0 ? 4 : pad);\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return textDecoder.decode(bytes);\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Payload encode / decode\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a `StickyCookiePayload` to its on-wire value. Does NOT include\n * the cookie name or attributes — see {@link serializeCookie}.\n */\nexport function encodePayload(payload: StickyCookiePayload): string {\n const json = JSON.stringify({ v: payload.v, u: payload.u, a: payload.a });\n return base64urlEncode(json);\n}\n\n/**\n * Decode a base64url-encoded cookie value back to a `StickyCookiePayload`.\n * Returns `null` for anything that fails validation. Never throws.\n */\nexport function decodePayload(raw: string | undefined | null): StickyCookiePayload | null {\n if (typeof raw !== \"string\" || raw.length === 0) return null;\n if (raw.length > MAX_PAYLOAD_BYTES) return null;\n const json = base64urlDecode(raw);\n if (json === null) return null;\n if (json.length > MAX_PAYLOAD_BYTES) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return null;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n const obj = parsed as Record<string, unknown>;\n if (obj[\"v\"] !== 1) return null;\n if (typeof obj[\"u\"] !== \"string\" || obj[\"u\"].length === 0 || obj[\"u\"].length > 256) return null;\n const rawA = obj[\"a\"];\n if (rawA === null || typeof rawA !== \"object\" || Array.isArray(rawA)) return null;\n const src = rawA as Record<string, unknown>;\n const a: Record<string, string> = Object.create(null);\n for (const key of Object.keys(src)) {\n if (RESERVED_NAMES.has(key)) continue;\n if (key.length === 0 || key.length > 128) continue;\n const val = src[key];\n if (typeof val !== \"string\" || val.length === 0 || val.length > 128) continue;\n a[key] = val;\n }\n return { v: 1, u: obj[\"u\"] as string, a };\n}\n\n// ---------------------------------------------------------------------------\n// Cookie header parsing (hand-rolled tokenizer)\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a raw `Cookie:` header into a `null`-prototype map of\n * `name → value`. Tolerates leading whitespace, empty segments,\n * missing `=`, and rejects reserved names.\n */\nexport function parseCookieHeader(header: string | undefined | null): Record<string, string> {\n const out: Record<string, string> = Object.create(null);\n if (typeof header !== \"string\" || header.length === 0) return out;\n if (header.length > MAX_COOKIE_HEADER_BYTES) return out;\n\n let i = 0;\n const n = header.length;\n while (i < n) {\n // Skip leading whitespace in this segment.\n while (i < n && (header.charCodeAt(i) === 0x20 || header.charCodeAt(i) === 0x09)) i++;\n const start = i;\n while (i < n && header.charCodeAt(i) !== 0x3b /* ; */) i++;\n const segment = header.slice(start, i);\n if (i < n) i++; // consume `;`\n if (segment.length === 0) continue;\n const eq = segment.indexOf(\"=\");\n if (eq <= 0) continue;\n const name = segment.slice(0, eq).trim();\n if (name.length === 0) continue;\n if (RESERVED_NAMES.has(name)) continue;\n let value = segment.slice(eq + 1).trim();\n // Cookies sometimes come wrapped in double quotes.\n if (\n value.length >= 2 &&\n value.charCodeAt(0) === 0x22 &&\n value.charCodeAt(value.length - 1) === 0x22\n ) {\n value = value.slice(1, -1);\n }\n // Keep first occurrence of a duplicate name.\n if (out[name] === undefined) {\n out[name] = safeDecodeURIComponent(value);\n }\n }\n return out;\n}\n\nfunction safeDecodeURIComponent(s: string): string {\n try {\n return decodeURIComponent(s);\n } catch {\n return s;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cookie serialization (Set-Cookie value builder)\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `Set-Cookie` header value: `name=value; attr=...; ...`.\n * Cookie value is URL-encoded so any non-ASCII bytes round-trip through\n * a standards-compliant parser.\n */\nexport function serializeCookie(\n name: string,\n value: string,\n options: VariantLabServerOptions & { readonly secure?: boolean } = {},\n): string {\n const parts: string[] = [`${name}=${encodeURIComponent(value)}`];\n const maxAge = options.maxAge ?? DEFAULT_MAX_AGE;\n if (maxAge > 0) parts.push(`Max-Age=${Math.floor(maxAge)}`);\n parts.push(`Path=${options.path ?? \"/\"}`);\n const sameSite = options.sameSite ?? \"lax\";\n parts.push(`SameSite=${capitalize(sameSite)}`);\n if (options.httpOnly !== false) parts.push(\"HttpOnly\");\n if (options.secure === true) parts.push(\"Secure\");\n if (options.domain !== undefined) parts.push(`Domain=${options.domain}`);\n return parts.join(\"; \");\n}\n\nfunction capitalize(s: string): string {\n if (s.length === 0) return s;\n return s[0]!.toUpperCase() + s.slice(1);\n}\n\n// ---------------------------------------------------------------------------\n// Request-source adapters\n// ---------------------------------------------------------------------------\n\n/**\n * Read a named cookie from any supported source: `Request`, Next's\n * `ReadonlyRequestCookies`, a Pages Router `NextApiRequest`, or a\n * raw header string. Returns `undefined` when missing.\n */\nexport function readCookieFromSource(\n source: CookieSource,\n name: string = DEFAULT_COOKIE_NAME,\n): string | undefined {\n if (source === null || source === undefined) return undefined;\n\n if (typeof source === \"string\") {\n const parsed = parseCookieHeader(source);\n return parsed[name];\n }\n\n // `ReadonlyRequestCookies` shape.\n if (typeof (source as RequestCookieJar).get === \"function\") {\n const entry = (source as RequestCookieJar).get(name);\n return entry === undefined ? undefined : entry.value;\n }\n\n // Fetch `Request`.\n if (typeof (source as Request).headers?.get === \"function\") {\n const header = (source as Request).headers.get(\"cookie\");\n if (header === null) return undefined;\n return parseCookieHeader(header)[name];\n }\n\n // Pages Router `req` shape.\n const pagesReq = source as PagesRouterRequestLike;\n if (pagesReq.cookies !== undefined) {\n const fromBag = pagesReq.cookies[name];\n if (typeof fromBag === \"string\" && fromBag.length > 0) return fromBag;\n }\n if (pagesReq.headers !== undefined) {\n const header = pagesReq.headers[\"cookie\"];\n if (typeof header === \"string\") return parseCookieHeader(header)[name];\n if (Array.isArray(header) && typeof header[0] === \"string\") {\n return parseCookieHeader(header[0])[name];\n }\n }\n return undefined;\n}\n\n/**\n * Full helper: read the sticky payload from a source, returning\n * `null` if the cookie is missing, malformed, or fails validation.\n */\nexport function readPayloadFromSource(\n source: CookieSource,\n name: string = DEFAULT_COOKIE_NAME,\n): StickyCookiePayload | null {\n const raw = readCookieFromSource(source, name);\n return decodePayload(raw);\n}\n\n// ---------------------------------------------------------------------------\n// User ID generation (Web Crypto — available on every target runtime)\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a v4 UUID via `crypto.randomUUID`, falling back to a\n * hand-formatted v4 built from `crypto.getRandomValues`. Both APIs\n * are Web Crypto and are available on every Phase 1 target runtime.\n */\nexport function generateUserId(): string {\n const g = globalThis as {\n crypto?: {\n randomUUID?: () => string;\n getRandomValues?: <T extends ArrayBufferView>(arr: T) => T;\n };\n };\n if (g.crypto?.randomUUID !== undefined) return g.crypto.randomUUID();\n if (g.crypto?.getRandomValues !== undefined) {\n const bytes = new Uint8Array(16);\n g.crypto.getRandomValues(bytes);\n // Set version (4) and variant (10xx) bits.\n bytes[6] = (bytes[6]! & 0x0f) | 0x40;\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n const hex: string[] = [];\n for (let i = 0; i < 16; i++) {\n hex.push((bytes[i] as number).toString(16).padStart(2, \"0\"));\n }\n return `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex.slice(6, 8).join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10, 16).join(\"\")}`;\n }\n // Last-ditch fallback: timestamp + Math.random. Not cryptographically\n // strong, but deterministic per-visit user IDs are not a security boundary.\n return `u-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;\n}\n\n/** Exported so adapters can override the default name. */\nexport { DEFAULT_COOKIE_NAME, DEFAULT_MAX_AGE } from \"../types.js\";\n","/**\n * `createVariantLabServer(config, options?)` — factory that validates the\n * config once and returns request-scoped helpers. Each call to\n * `getVariant` / `getVariantValue` constructs a short-lived engine\n * seeded from the request's cookie so concurrent HTTP requests never\n * share mutable state.\n *\n * Validation is expensive; engine construction is cheap (a few Maps,\n * no I/O, no allocation beyond what's already in the frozen config).\n */\n\nimport {\n createEngine,\n type ExperimentsConfig,\n type VariantContext,\n validateConfig,\n} from \"@variantlab/core\";\nimport type {\n CookieSource,\n StickyCookiePayload,\n VariantLabProviderProps,\n VariantLabServerOptions,\n} from \"../types.js\";\nimport { DEFAULT_COOKIE_NAME } from \"../types.js\";\nimport {\n decodePayload,\n encodePayload,\n generateUserId,\n readPayloadFromSource,\n serializeCookie,\n} from \"./cookie.js\";\n\nexport interface VariantLabServer {\n /** The frozen, validated config this server was built with. */\n readonly config: ExperimentsConfig;\n\n /**\n * Resolve a variant id using the given cookie source + optional\n * context extras. Always returns the default if anything fails.\n */\n getVariant(experimentId: string, source: CookieSource, context?: VariantContext): string;\n\n /**\n * Resolve a variant's `value` payload. Equivalent to calling\n * `getVariant` + looking up the variant by id.\n */\n getVariantValue<T = unknown>(\n experimentId: string,\n source: CookieSource,\n context?: VariantContext,\n ): T;\n\n /**\n * Decode the sticky cookie. Returns `null` when missing or invalid.\n * Layouts pass this directly to `<VariantLabProvider initialVariants={...}>`.\n */\n readPayload(source: CookieSource): StickyCookiePayload | null;\n\n /**\n * Build a `Set-Cookie` header value for a freshly-computed payload.\n * Caller is responsible for attaching it to the outgoing response.\n */\n writePayload(payload: StickyCookiePayload, secure?: boolean): string;\n\n /**\n * Build the `initialContext` + `initialVariants` props that the\n * Next `<VariantLabProvider>` needs. Convenience for layouts:\n *\n * const props = server.toProviderProps(cookies(), { locale: \"en\" });\n * return <VariantLabProvider {...props}>{children}</VariantLabProvider>;\n */\n toProviderProps(\n source: CookieSource,\n contextExtras?: VariantContext,\n ): Omit<VariantLabProviderProps, \"children\" | \"config\">;\n}\n\nexport interface CreateVariantLabServerOptions extends VariantLabServerOptions {\n /**\n * Called when cookie reads or engine construction fails catastrophically.\n * Defaults to a no-op (fail-open).\n */\n readonly onError?: (error: Error) => void;\n}\n\n/**\n * Validate the supplied config once and return a factory whose methods\n * build a new `VariantEngine` per call. This is the entry point that\n * layouts, server components, route handlers, and `getServerSideProps`\n * should use when they want to resolve variants at SSR time.\n */\nexport function createVariantLabServer(\n rawConfig: unknown,\n options: CreateVariantLabServerOptions = {},\n): VariantLabServer {\n const config = validateConfig(rawConfig);\n const cookieName = options.cookieName ?? DEFAULT_COOKIE_NAME;\n const onError = options.onError ?? (() => {});\n\n function readPayload(source: CookieSource): StickyCookiePayload | null {\n try {\n return readPayloadFromSource(source, cookieName);\n } catch (error) {\n onError(error as Error);\n return null;\n }\n }\n\n function buildContext(\n payload: StickyCookiePayload | null,\n extras: VariantContext | undefined,\n ): VariantContext {\n const userId = payload?.u ?? extras?.userId;\n if (userId === undefined) return { ...extras };\n return { ...extras, userId };\n }\n\n function getVariant(\n experimentId: string,\n source: CookieSource,\n context?: VariantContext,\n ): string {\n try {\n const payload = readPayload(source);\n const engine = createEngine(config, {\n context: buildContext(payload, context),\n ...(payload?.a !== undefined ? { initialAssignments: payload.a } : {}),\n });\n return engine.getVariant(experimentId);\n } catch (error) {\n onError(error as Error);\n return experimentDefault(config, experimentId);\n }\n }\n\n function getVariantValue<T = unknown>(\n experimentId: string,\n source: CookieSource,\n context?: VariantContext,\n ): T {\n try {\n const payload = readPayload(source);\n const engine = createEngine(config, {\n context: buildContext(payload, context),\n ...(payload?.a !== undefined ? { initialAssignments: payload.a } : {}),\n });\n return engine.getVariantValue<T>(experimentId);\n } catch (error) {\n onError(error as Error);\n return undefined as T;\n }\n }\n\n function writePayload(payload: StickyCookiePayload, secure?: boolean): string {\n const value = encodePayload(payload);\n return serializeCookie(cookieName, value, {\n ...options,\n ...(secure !== undefined ? { secure } : {}),\n });\n }\n\n function toProviderProps(\n source: CookieSource,\n contextExtras?: VariantContext,\n ): Omit<VariantLabProviderProps, \"children\" | \"config\"> {\n const payload = readPayload(source);\n const initialContext = buildContext(payload, contextExtras);\n // `payload.a` is a null-prototype object (see `decodePayload`) for\n // prototype-pollution safety during internal lookups. React Server\n // Components refuse to serialize null-prototype objects when passing\n // props across the Server→Client boundary, so we shallow-copy into a\n // plain object here. Values are strings-only per the decoder, so a\n // spread is sufficient.\n const initialVariants: Record<string, string> = payload?.a ? { ...payload.a } : {};\n return { initialContext, initialVariants };\n }\n\n return {\n config,\n getVariant,\n getVariantValue,\n readPayload,\n writePayload,\n toProviderProps,\n };\n}\n\nfunction experimentDefault(config: ExperimentsConfig, experimentId: string): string {\n const exp = config.experiments.find((e) => e.id === experimentId);\n return exp?.default ?? \"\";\n}\n\n/** Re-exported helpers for advanced users who only want the codecs. */\nexport { decodePayload, encodePayload, generateUserId };\n","/**\n * `getVariantSSR` / `getVariantValueSSR` — per-request SSR helpers that\n * don't require the caller to hold a `VariantLabServer` instance.\n *\n * Internally they use a `WeakMap` keyed on the raw config object identity\n * so repeat calls with the same imported JSON module don't re-validate\n * the config. The WeakMap doesn't retain anything — if the caller drops\n * the config reference, the cached server is GC'd.\n *\n * Signature matches `API.md` lines 569–582 (synchronous). See the\n * package README for notes on spec drift vs. the Session 7 prompt.\n */\n\nimport type { ExperimentsConfig, VariantContext } from \"@variantlab/core\";\nimport type { CookieSource } from \"../types.js\";\nimport {\n type CreateVariantLabServerOptions,\n createVariantLabServer,\n type VariantLabServer,\n} from \"./create-variant-lab-server.js\";\n\nconst cache = new WeakMap<object, VariantLabServer>();\n\nfunction resolveServer(\n rawConfig: unknown,\n options: CreateVariantLabServerOptions | undefined,\n): VariantLabServer {\n if (rawConfig !== null && typeof rawConfig === \"object\") {\n const hit = cache.get(rawConfig as object);\n if (hit !== undefined) return hit;\n }\n const server = createVariantLabServer(rawConfig, options);\n if (rawConfig !== null && typeof rawConfig === \"object\") {\n cache.set(rawConfig as object, server);\n }\n return server;\n}\n\n/**\n * Resolve a variant for the current request. Reads the sticky cookie\n * from the supplied source (`Request`, `ReadonlyRequestCookies`,\n * `NextApiRequest`, or a raw cookie header string).\n *\n * Synchronous. In Next 15, `cookies()` is async — await it and pass the\n * resolved store into this function.\n */\nexport function getVariantSSR(\n experimentId: string,\n source: CookieSource,\n config: unknown | ExperimentsConfig,\n options?: CreateVariantLabServerOptions & { readonly context?: VariantContext },\n): string {\n const server = resolveServer(config, options);\n return server.getVariant(experimentId, source, options?.context);\n}\n\n/** Variant-value equivalent of {@link getVariantSSR}. */\nexport function getVariantValueSSR<T = unknown>(\n experimentId: string,\n source: CookieSource,\n config: unknown | ExperimentsConfig,\n options?: CreateVariantLabServerOptions & { readonly context?: VariantContext },\n): T {\n const server = resolveServer(config, options);\n return server.getVariantValue<T>(experimentId, source, options?.context);\n}\n","/**\n * `variantLabMiddleware(config, options?)` — Next.js middleware factory.\n *\n * Responsibilities (Phase 1):\n * 1. Read the sticky cookie from the incoming request.\n * 2. If missing or malformed, mint a fresh userId and write an\n * empty payload `{ v: 1, u: userId, a: {} }` on the outgoing\n * response. Assignments are NOT computed at the edge.\n * 3. Fail-open: any error → pass through unmodified.\n *\n * The factory takes the raw config only so it can validate it once at\n * import time — middleware is instantiated per process, not per request.\n * It does not resolve any experiments during middleware execution.\n *\n * Designed to run on the Vercel Edge runtime (`export const runtime = \"edge\"`).\n * No Node-only APIs. No `process.env` access.\n */\n\nimport { validateConfig } from \"@variantlab/core\";\nimport type { StickyCookiePayload, VariantLabServerOptions } from \"../types.js\";\nimport { DEFAULT_COOKIE_NAME } from \"../types.js\";\nimport {\n decodePayload,\n encodePayload,\n generateUserId,\n parseCookieHeader,\n serializeCookie,\n} from \"./cookie.js\";\n\n/**\n * The minimal shape we need from `NextRequest`. Avoids a hard\n * dependency on `next/server` at type-check time.\n */\ninterface NextRequestLike {\n readonly headers: { get(name: string): string | null };\n readonly nextUrl: { readonly protocol: string };\n}\n\n/**\n * The minimal shape we produce / consume for `NextResponse`. Matches\n * `NextResponse.next()` / `NextResponse.redirect()` return values.\n */\ninterface NextResponseLike {\n readonly headers: { append(name: string, value: string): void };\n}\n\nexport interface VariantLabMiddlewareOptions extends VariantLabServerOptions {\n /**\n * Called on any caught error. Defaults to a no-op so the middleware\n * remains fail-open. Provide a logger here if you want visibility.\n */\n readonly onError?: (error: Error) => void;\n}\n\n/**\n * Build a middleware function. Accepts the raw response factory from\n * the caller so we don't need to import `NextResponse` directly (that\n * would drag `next/server` into every consumer's bundle).\n *\n * Typical usage in `middleware.ts`:\n *\n * import { NextResponse } from \"next/server\";\n * import experiments from \"./experiments.json\";\n * import { variantLabMiddleware } from \"@variantlab/next\";\n *\n * const middleware = variantLabMiddleware(experiments);\n *\n * export default function (req) {\n * return middleware(req, NextResponse.next());\n * }\n *\n * The factory also exports `middleware.handle(req, nextResponseFactory)`\n * for the more idiomatic style where NextResponse is only imported once.\n */\nexport function variantLabMiddleware(\n rawConfig: unknown,\n options: VariantLabMiddlewareOptions = {},\n) {\n // Validate once at import time so schema errors surface at build time,\n // not on the first request. The validated value is unused by the\n // per-request path, but we keep it captured to hold a strong reference.\n let frozen = true;\n try {\n validateConfig(rawConfig);\n } catch (error) {\n options.onError?.(error as Error);\n frozen = false;\n }\n\n const cookieName = options.cookieName ?? DEFAULT_COOKIE_NAME;\n const onError = options.onError ?? (() => {});\n\n return function apply<TResponse extends NextResponseLike>(\n req: NextRequestLike,\n response: TResponse,\n ): TResponse {\n if (!frozen) return response;\n try {\n const header = req.headers.get(\"cookie\");\n const cookies = parseCookieHeader(header ?? \"\");\n const existing = decodePayload(cookies[cookieName]);\n if (existing !== null) return response;\n\n const payload: StickyCookiePayload = {\n v: 1,\n u: generateUserId(),\n a: {},\n };\n const secure = req.nextUrl.protocol === \"https:\";\n const setCookie = serializeCookie(cookieName, encodePayload(payload), {\n ...options,\n secure,\n });\n response.headers.append(\"set-cookie\", setCookie);\n return response;\n } catch (error) {\n onError(error as Error);\n return response;\n }\n };\n}\n","/**\n * `@variantlab/next` — server barrel.\n *\n * This file is the default entrypoint when consumers write\n * `import { ... } from \"@variantlab/next\"`. It exposes only\n * server-safe helpers (no `\"use client\"`, no React). Use\n * `@variantlab/next/client` for the provider and hooks.\n *\n * Edge-runtime compatible: no Node-only APIs, no `process.env`, and\n * no runtime dependencies beyond `@variantlab/core`.\n */\n\nexport const VERSION = \"0.0.0\";\n\n// Re-export core types so consumers can `import type { ExperimentsConfig }\n// from \"@variantlab/next\"` without reaching into core directly.\nexport type {\n AssignmentStrategy,\n Experiment,\n ExperimentsConfig,\n Variant,\n VariantContext,\n} from \"@variantlab/core\";\n// Cookie codec + request adapters\nexport {\n decodePayload,\n encodePayload,\n generateUserId,\n parseCookieHeader,\n readCookieFromSource,\n readPayloadFromSource,\n serializeCookie,\n} from \"./server/cookie.js\";\n// Server-side resolution\nexport {\n type CreateVariantLabServerOptions,\n createVariantLabServer,\n type VariantLabServer,\n} from \"./server/create-variant-lab-server.js\";\nexport { getVariantSSR, getVariantValueSSR } from \"./server/get-variant-ssr.js\";\n// Middleware factory\nexport {\n type VariantLabMiddlewareOptions,\n variantLabMiddleware,\n} from \"./server/middleware.js\";\n// Types\nexport type {\n CookieSource,\n PagesRouterRequestLike,\n RequestCookieJar,\n StickyCookiePayload,\n VariantLabProviderProps,\n VariantLabServerOptions,\n} from \"./types.js\";\nexport { DEFAULT_COOKIE_NAME, DEFAULT_MAX_AGE } from \"./types.js\";\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { ExperimentsConfig, VariantContext } from '@variantlab/core';
|
|
2
|
+
export { AssignmentStrategy, Experiment, ExperimentsConfig, Variant, VariantContext } from '@variantlab/core';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared types for `@variantlab/next`.
|
|
7
|
+
*
|
|
8
|
+
* Kept in a single tiny file so every entrypoint (server barrel,
|
|
9
|
+
* app-router subpath, pages-router subpath, client provider) can
|
|
10
|
+
* import without pulling in code.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* On-wire cookie payload shape. Short keys to minimize cookie bytes.
|
|
15
|
+
*
|
|
16
|
+
* v — schema version (currently 1)
|
|
17
|
+
* u — userId (required; generated on first visit by middleware)
|
|
18
|
+
* a — assignments: { [experimentId]: variantId } (may be empty)
|
|
19
|
+
*/
|
|
20
|
+
interface StickyCookiePayload {
|
|
21
|
+
readonly v: 1;
|
|
22
|
+
readonly u: string;
|
|
23
|
+
readonly a: Readonly<Record<string, string>>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Anything from which we can read an HTTP Cookie header. Supports:
|
|
27
|
+
*
|
|
28
|
+
* - Fetch-style `Request` (App Router Route Handlers, middleware)
|
|
29
|
+
* - `NextApiRequest` / Pages Router `req` (has `.cookies` and `.headers`)
|
|
30
|
+
* - Next 14+ `ReadonlyRequestCookies` from `cookies()` in `next/headers`
|
|
31
|
+
* - A plain cookie header string
|
|
32
|
+
*/
|
|
33
|
+
type CookieSource = Request | RequestCookieJar | PagesRouterRequestLike | string | null | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Shape we rely on from Next's `cookies()` return value. Kept minimal
|
|
36
|
+
* so we don't depend on `next/headers` types at compile time — the
|
|
37
|
+
* server barrel must be framework-agnostic enough to tree-shake.
|
|
38
|
+
*/
|
|
39
|
+
interface RequestCookieJar {
|
|
40
|
+
get(name: string): {
|
|
41
|
+
readonly value: string;
|
|
42
|
+
} | undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Minimal Pages Router / `NextApiRequest` shape. Avoids importing
|
|
46
|
+
* `next` types in the base entrypoint.
|
|
47
|
+
*/
|
|
48
|
+
interface PagesRouterRequestLike {
|
|
49
|
+
readonly cookies?: Readonly<Record<string, string | undefined>>;
|
|
50
|
+
readonly headers?: Readonly<Record<string, string | string[] | undefined>>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Options accepted by `createVariantLabServer` and the SSR helpers.
|
|
54
|
+
*/
|
|
55
|
+
interface VariantLabServerOptions {
|
|
56
|
+
/** Cookie name. Defaults to `__variantlab_sticky`. */
|
|
57
|
+
readonly cookieName?: string;
|
|
58
|
+
/** Max age in seconds. Defaults to 365 days. */
|
|
59
|
+
readonly maxAge?: number;
|
|
60
|
+
/** Cookie path. Defaults to `/`. */
|
|
61
|
+
readonly path?: string;
|
|
62
|
+
/** `SameSite` attribute. Defaults to `"lax"`. */
|
|
63
|
+
readonly sameSite?: "strict" | "lax" | "none";
|
|
64
|
+
/** Override for the `Secure` flag. When `undefined`, derived from the request URL. */
|
|
65
|
+
readonly secure?: boolean;
|
|
66
|
+
/** `HttpOnly` flag. Defaults to `true`. */
|
|
67
|
+
readonly httpOnly?: boolean;
|
|
68
|
+
/** `Domain` attribute. Defaults to undefined (host-only cookie). */
|
|
69
|
+
readonly domain?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Props accepted by the Next `VariantLabProvider` Client Component.
|
|
73
|
+
*/
|
|
74
|
+
interface VariantLabProviderProps {
|
|
75
|
+
/** Validated `ExperimentsConfig` or a raw JSON module import. */
|
|
76
|
+
readonly config: unknown | ExperimentsConfig;
|
|
77
|
+
/** Runtime context (userId, locale, platform, …) applied before first render. */
|
|
78
|
+
readonly initialContext?: VariantContext;
|
|
79
|
+
/**
|
|
80
|
+
* Assignments computed on the server. Seeded into the engine cache so
|
|
81
|
+
* the first `getVariant` call on the client returns the same variant
|
|
82
|
+
* that was server-rendered, without re-evaluating targeting.
|
|
83
|
+
*/
|
|
84
|
+
readonly initialVariants?: Readonly<Record<string, string>>;
|
|
85
|
+
readonly children?: ReactNode;
|
|
86
|
+
}
|
|
87
|
+
/** Default cookie name. Shared between server helpers and middleware. */
|
|
88
|
+
declare const DEFAULT_COOKIE_NAME = "__variantlab_sticky";
|
|
89
|
+
/** Default max age (365 days, in seconds). */
|
|
90
|
+
declare const DEFAULT_MAX_AGE: number;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Hand-rolled cookie codec + parser for `@variantlab/next`.
|
|
94
|
+
*
|
|
95
|
+
* Goals:
|
|
96
|
+
* 1. Zero runtime dependencies. No `cookie` package, no `js-base64`.
|
|
97
|
+
* 2. Edge-runtime safe. Uses only `TextEncoder`/`TextDecoder`,
|
|
98
|
+
* `globalThis.crypto`, standard `atob`/`btoa`, and `Object.create(null)`
|
|
99
|
+
* — all available on Vercel Edge, Cloudflare Workers, Deno, Bun, Node 18+.
|
|
100
|
+
* 3. Prototype-pollution hardened. Mirrors the guards in
|
|
101
|
+
* `packages/core/src/config/validator.ts`: cookie parser rejects
|
|
102
|
+
* `__proto__`, `constructor`, `prototype` as cookie names, and the
|
|
103
|
+
* JSON payload is parsed through `Object.create(null)` sanitization.
|
|
104
|
+
* 4. Size-capped. A malicious client sending a 10 MB Cookie header gets
|
|
105
|
+
* rejected before allocation by `MAX_COOKIE_HEADER_BYTES`.
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Encode a `StickyCookiePayload` to its on-wire value. Does NOT include
|
|
110
|
+
* the cookie name or attributes — see {@link serializeCookie}.
|
|
111
|
+
*/
|
|
112
|
+
declare function encodePayload(payload: StickyCookiePayload): string;
|
|
113
|
+
/**
|
|
114
|
+
* Decode a base64url-encoded cookie value back to a `StickyCookiePayload`.
|
|
115
|
+
* Returns `null` for anything that fails validation. Never throws.
|
|
116
|
+
*/
|
|
117
|
+
declare function decodePayload(raw: string | undefined | null): StickyCookiePayload | null;
|
|
118
|
+
/**
|
|
119
|
+
* Parse a raw `Cookie:` header into a `null`-prototype map of
|
|
120
|
+
* `name → value`. Tolerates leading whitespace, empty segments,
|
|
121
|
+
* missing `=`, and rejects reserved names.
|
|
122
|
+
*/
|
|
123
|
+
declare function parseCookieHeader(header: string | undefined | null): Record<string, string>;
|
|
124
|
+
/**
|
|
125
|
+
* Build a `Set-Cookie` header value: `name=value; attr=...; ...`.
|
|
126
|
+
* Cookie value is URL-encoded so any non-ASCII bytes round-trip through
|
|
127
|
+
* a standards-compliant parser.
|
|
128
|
+
*/
|
|
129
|
+
declare function serializeCookie(name: string, value: string, options?: VariantLabServerOptions & {
|
|
130
|
+
readonly secure?: boolean;
|
|
131
|
+
}): string;
|
|
132
|
+
/**
|
|
133
|
+
* Read a named cookie from any supported source: `Request`, Next's
|
|
134
|
+
* `ReadonlyRequestCookies`, a Pages Router `NextApiRequest`, or a
|
|
135
|
+
* raw header string. Returns `undefined` when missing.
|
|
136
|
+
*/
|
|
137
|
+
declare function readCookieFromSource(source: CookieSource, name?: string): string | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Full helper: read the sticky payload from a source, returning
|
|
140
|
+
* `null` if the cookie is missing, malformed, or fails validation.
|
|
141
|
+
*/
|
|
142
|
+
declare function readPayloadFromSource(source: CookieSource, name?: string): StickyCookiePayload | null;
|
|
143
|
+
/**
|
|
144
|
+
* Generate a v4 UUID via `crypto.randomUUID`, falling back to a
|
|
145
|
+
* hand-formatted v4 built from `crypto.getRandomValues`. Both APIs
|
|
146
|
+
* are Web Crypto and are available on every Phase 1 target runtime.
|
|
147
|
+
*/
|
|
148
|
+
declare function generateUserId(): string;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* `createVariantLabServer(config, options?)` — factory that validates the
|
|
152
|
+
* config once and returns request-scoped helpers. Each call to
|
|
153
|
+
* `getVariant` / `getVariantValue` constructs a short-lived engine
|
|
154
|
+
* seeded from the request's cookie so concurrent HTTP requests never
|
|
155
|
+
* share mutable state.
|
|
156
|
+
*
|
|
157
|
+
* Validation is expensive; engine construction is cheap (a few Maps,
|
|
158
|
+
* no I/O, no allocation beyond what's already in the frozen config).
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
interface VariantLabServer {
|
|
162
|
+
/** The frozen, validated config this server was built with. */
|
|
163
|
+
readonly config: ExperimentsConfig;
|
|
164
|
+
/**
|
|
165
|
+
* Resolve a variant id using the given cookie source + optional
|
|
166
|
+
* context extras. Always returns the default if anything fails.
|
|
167
|
+
*/
|
|
168
|
+
getVariant(experimentId: string, source: CookieSource, context?: VariantContext): string;
|
|
169
|
+
/**
|
|
170
|
+
* Resolve a variant's `value` payload. Equivalent to calling
|
|
171
|
+
* `getVariant` + looking up the variant by id.
|
|
172
|
+
*/
|
|
173
|
+
getVariantValue<T = unknown>(experimentId: string, source: CookieSource, context?: VariantContext): T;
|
|
174
|
+
/**
|
|
175
|
+
* Decode the sticky cookie. Returns `null` when missing or invalid.
|
|
176
|
+
* Layouts pass this directly to `<VariantLabProvider initialVariants={...}>`.
|
|
177
|
+
*/
|
|
178
|
+
readPayload(source: CookieSource): StickyCookiePayload | null;
|
|
179
|
+
/**
|
|
180
|
+
* Build a `Set-Cookie` header value for a freshly-computed payload.
|
|
181
|
+
* Caller is responsible for attaching it to the outgoing response.
|
|
182
|
+
*/
|
|
183
|
+
writePayload(payload: StickyCookiePayload, secure?: boolean): string;
|
|
184
|
+
/**
|
|
185
|
+
* Build the `initialContext` + `initialVariants` props that the
|
|
186
|
+
* Next `<VariantLabProvider>` needs. Convenience for layouts:
|
|
187
|
+
*
|
|
188
|
+
* const props = server.toProviderProps(cookies(), { locale: "en" });
|
|
189
|
+
* return <VariantLabProvider {...props}>{children}</VariantLabProvider>;
|
|
190
|
+
*/
|
|
191
|
+
toProviderProps(source: CookieSource, contextExtras?: VariantContext): Omit<VariantLabProviderProps, "children" | "config">;
|
|
192
|
+
}
|
|
193
|
+
interface CreateVariantLabServerOptions extends VariantLabServerOptions {
|
|
194
|
+
/**
|
|
195
|
+
* Called when cookie reads or engine construction fails catastrophically.
|
|
196
|
+
* Defaults to a no-op (fail-open).
|
|
197
|
+
*/
|
|
198
|
+
readonly onError?: (error: Error) => void;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Validate the supplied config once and return a factory whose methods
|
|
202
|
+
* build a new `VariantEngine` per call. This is the entry point that
|
|
203
|
+
* layouts, server components, route handlers, and `getServerSideProps`
|
|
204
|
+
* should use when they want to resolve variants at SSR time.
|
|
205
|
+
*/
|
|
206
|
+
declare function createVariantLabServer(rawConfig: unknown, options?: CreateVariantLabServerOptions): VariantLabServer;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* `getVariantSSR` / `getVariantValueSSR` — per-request SSR helpers that
|
|
210
|
+
* don't require the caller to hold a `VariantLabServer` instance.
|
|
211
|
+
*
|
|
212
|
+
* Internally they use a `WeakMap` keyed on the raw config object identity
|
|
213
|
+
* so repeat calls with the same imported JSON module don't re-validate
|
|
214
|
+
* the config. The WeakMap doesn't retain anything — if the caller drops
|
|
215
|
+
* the config reference, the cached server is GC'd.
|
|
216
|
+
*
|
|
217
|
+
* Signature matches `API.md` lines 569–582 (synchronous). See the
|
|
218
|
+
* package README for notes on spec drift vs. the Session 7 prompt.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resolve a variant for the current request. Reads the sticky cookie
|
|
223
|
+
* from the supplied source (`Request`, `ReadonlyRequestCookies`,
|
|
224
|
+
* `NextApiRequest`, or a raw cookie header string).
|
|
225
|
+
*
|
|
226
|
+
* Synchronous. In Next 15, `cookies()` is async — await it and pass the
|
|
227
|
+
* resolved store into this function.
|
|
228
|
+
*/
|
|
229
|
+
declare function getVariantSSR(experimentId: string, source: CookieSource, config: unknown | ExperimentsConfig, options?: CreateVariantLabServerOptions & {
|
|
230
|
+
readonly context?: VariantContext;
|
|
231
|
+
}): string;
|
|
232
|
+
/** Variant-value equivalent of {@link getVariantSSR}. */
|
|
233
|
+
declare function getVariantValueSSR<T = unknown>(experimentId: string, source: CookieSource, config: unknown | ExperimentsConfig, options?: CreateVariantLabServerOptions & {
|
|
234
|
+
readonly context?: VariantContext;
|
|
235
|
+
}): T;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* `variantLabMiddleware(config, options?)` — Next.js middleware factory.
|
|
239
|
+
*
|
|
240
|
+
* Responsibilities (Phase 1):
|
|
241
|
+
* 1. Read the sticky cookie from the incoming request.
|
|
242
|
+
* 2. If missing or malformed, mint a fresh userId and write an
|
|
243
|
+
* empty payload `{ v: 1, u: userId, a: {} }` on the outgoing
|
|
244
|
+
* response. Assignments are NOT computed at the edge.
|
|
245
|
+
* 3. Fail-open: any error → pass through unmodified.
|
|
246
|
+
*
|
|
247
|
+
* The factory takes the raw config only so it can validate it once at
|
|
248
|
+
* import time — middleware is instantiated per process, not per request.
|
|
249
|
+
* It does not resolve any experiments during middleware execution.
|
|
250
|
+
*
|
|
251
|
+
* Designed to run on the Vercel Edge runtime (`export const runtime = "edge"`).
|
|
252
|
+
* No Node-only APIs. No `process.env` access.
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The minimal shape we need from `NextRequest`. Avoids a hard
|
|
257
|
+
* dependency on `next/server` at type-check time.
|
|
258
|
+
*/
|
|
259
|
+
interface NextRequestLike {
|
|
260
|
+
readonly headers: {
|
|
261
|
+
get(name: string): string | null;
|
|
262
|
+
};
|
|
263
|
+
readonly nextUrl: {
|
|
264
|
+
readonly protocol: string;
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The minimal shape we produce / consume for `NextResponse`. Matches
|
|
269
|
+
* `NextResponse.next()` / `NextResponse.redirect()` return values.
|
|
270
|
+
*/
|
|
271
|
+
interface NextResponseLike {
|
|
272
|
+
readonly headers: {
|
|
273
|
+
append(name: string, value: string): void;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
interface VariantLabMiddlewareOptions extends VariantLabServerOptions {
|
|
277
|
+
/**
|
|
278
|
+
* Called on any caught error. Defaults to a no-op so the middleware
|
|
279
|
+
* remains fail-open. Provide a logger here if you want visibility.
|
|
280
|
+
*/
|
|
281
|
+
readonly onError?: (error: Error) => void;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Build a middleware function. Accepts the raw response factory from
|
|
285
|
+
* the caller so we don't need to import `NextResponse` directly (that
|
|
286
|
+
* would drag `next/server` into every consumer's bundle).
|
|
287
|
+
*
|
|
288
|
+
* Typical usage in `middleware.ts`:
|
|
289
|
+
*
|
|
290
|
+
* import { NextResponse } from "next/server";
|
|
291
|
+
* import experiments from "./experiments.json";
|
|
292
|
+
* import { variantLabMiddleware } from "@variantlab/next";
|
|
293
|
+
*
|
|
294
|
+
* const middleware = variantLabMiddleware(experiments);
|
|
295
|
+
*
|
|
296
|
+
* export default function (req) {
|
|
297
|
+
* return middleware(req, NextResponse.next());
|
|
298
|
+
* }
|
|
299
|
+
*
|
|
300
|
+
* The factory also exports `middleware.handle(req, nextResponseFactory)`
|
|
301
|
+
* for the more idiomatic style where NextResponse is only imported once.
|
|
302
|
+
*/
|
|
303
|
+
declare function variantLabMiddleware(rawConfig: unknown, options?: VariantLabMiddlewareOptions): <TResponse extends NextResponseLike>(req: NextRequestLike, response: TResponse) => TResponse;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* `@variantlab/next` — server barrel.
|
|
307
|
+
*
|
|
308
|
+
* This file is the default entrypoint when consumers write
|
|
309
|
+
* `import { ... } from "@variantlab/next"`. It exposes only
|
|
310
|
+
* server-safe helpers (no `"use client"`, no React). Use
|
|
311
|
+
* `@variantlab/next/client` for the provider and hooks.
|
|
312
|
+
*
|
|
313
|
+
* Edge-runtime compatible: no Node-only APIs, no `process.env`, and
|
|
314
|
+
* no runtime dependencies beyond `@variantlab/core`.
|
|
315
|
+
*/
|
|
316
|
+
declare const VERSION = "0.0.0";
|
|
317
|
+
|
|
318
|
+
export { type CookieSource, type CreateVariantLabServerOptions, DEFAULT_COOKIE_NAME, DEFAULT_MAX_AGE, type PagesRouterRequestLike, type RequestCookieJar, type StickyCookiePayload, VERSION, type VariantLabMiddlewareOptions, type VariantLabProviderProps, type VariantLabServer, type VariantLabServerOptions, createVariantLabServer, decodePayload, encodePayload, generateUserId, getVariantSSR, getVariantValueSSR, parseCookieHeader, readCookieFromSource, readPayloadFromSource, serializeCookie, variantLabMiddleware };
|