@zenmanage/nextjs 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/@zenmanage%2Fnextjs.svg)](https://www.npmjs.com/package/@zenmanage/nextjs)
4
4
  [![Build Status](https://github.com/zenmanage/zenmanage-nextjs/actions/workflows/ci.yml/badge.svg)](https://github.com/zenmanage/zenmanage-nextjs/actions/workflows/ci.yml)
5
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/b810a0a8c29f4af2a4013e29192b861e)](https://app.codacy.com/gh/zenmanage/zenmanage-nextjs/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
5
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
7
 
7
8
  Feature flags for Next.js. Evaluate flags in Server Components, route handlers, `getServerSideProps` and middleware, and hand the results to Client Components so the first paint is already right.
@@ -83,6 +84,8 @@ All of them take your server key as `environmentToken`, plus the optional `apiEn
83
84
  | `createServerClient(options)` | `Zenmanage` | Get the shared SDK client. See the note below. |
84
85
  | `clearServerClients()` | `void` | Drop every shared client and its cache. Useful in tests. |
85
86
 
87
+ `getFlagValue` returns `fallback` for a flag that doesn't exist, or when the API can't be reached. A `defaultValue` in the input, or an entry for the key in `defaults`, wins over it.
88
+
86
89
  ### Caching
87
90
 
88
91
  Clients are shared per configuration (token, endpoint, TTL, usage setting and logger) for the life of the server process or Edge isolate. Rules are cached for 60 seconds unless you set `cacheTtl`. After that the next call fetches them again, so a flag change reaches your server within a minute.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getServerFlag
3
- } from "./chunk-ZUB4F2DA.js";
3
+ } from "./chunk-PHODKVFM.js";
4
4
 
5
5
  // src/middleware.ts
6
6
  import { Attribute, Context } from "@zenmanage/sdk";
@@ -106,4 +106,4 @@ export {
106
106
  buildContextFromRequest,
107
107
  evaluateMiddlewareFlag
108
108
  };
109
- //# sourceMappingURL=chunk-PMTVMVXJ.js.map
109
+ //# sourceMappingURL=chunk-HDORXS72.js.map
@@ -97,7 +97,7 @@ function contextFromBootstrapPayload(payload) {
97
97
  }
98
98
 
99
99
  // package.json
100
- var version = "1.0.0";
100
+ var version = "1.0.1";
101
101
 
102
102
  // src/identity.ts
103
103
  var CLIENT_AGENT = "zenmanage-nextjs";
@@ -191,7 +191,8 @@ async function preloadServerFlags(input) {
191
191
  return manager;
192
192
  }
193
193
  async function getFlagValue(input, fallback) {
194
- const flag = await getServerFlag(input);
194
+ const hasOwnDefault = input.defaultValue !== void 0 || input.defaults?.has(input.key) === true;
195
+ const flag = await getServerFlag(hasOwnDefault ? input : { ...input, defaultValue: fallback });
195
196
  if (typeof fallback === "boolean") {
196
197
  return flag.asBool();
197
198
  }
@@ -234,4 +235,4 @@ export {
234
235
  getFlagValue,
235
236
  getBootstrapPayload
236
237
  };
237
- //# sourceMappingURL=chunk-ZUB4F2DA.js.map
238
+ //# sourceMappingURL=chunk-PHODKVFM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/bootstrap.ts","../src/types.ts","../package.json","../src/identity.ts","../src/defaults.ts"],"sourcesContent":["import {\n ConfigBuilder,\n DefaultsCollection,\n Zenmanage,\n type Flag,\n type FlagManager,\n type FlagValue,\n type Logger,\n} from '@zenmanage/sdk';\nimport { buildBootstrapPayload } from './bootstrap';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from './identity';\nimport {\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport { mergeDefaults } from './defaults';\n\nfunction buildClient(options: NextZenmanageClientOptions): Zenmanage {\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(options.environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION)\n .withCacheTtl(options.cacheTtl ?? DEFAULT_SERVER_CACHE_TTL);\n\n if (options.apiEndpoint) {\n builder.withApiEndpoint(options.apiEndpoint);\n }\n\n if (typeof options.enableUsageReporting === 'boolean') {\n builder.withUsageReporting(options.enableUsageReporting);\n }\n\n if (options.logger) {\n builder.withLogger(options.logger);\n }\n\n return new Zenmanage(builder.build());\n}\n\n// A client owns the rules cache, so building one per call would refetch the rules for every\n// flag on every request. Clients are shared per configuration instead, for the life of the\n// server process (or edge isolate). Loggers are compared by identity, hence the WeakMap.\nconst clientsWithoutLogger = new Map<string, Zenmanage>();\nlet clientsByLogger = new WeakMap<Logger, Map<string, Zenmanage>>();\n\nfunction configurationKey(options: NextZenmanageClientOptions): string {\n return JSON.stringify([\n options.environmentToken,\n options.apiEndpoint ?? null,\n options.cacheTtl ?? null,\n options.enableUsageReporting ?? null,\n ]);\n}\n\n/**\n * Returns the client for this configuration, creating it on first use. Calls with the same\n * token, endpoint, TTL, usage setting and logger share one client and so one rules cache.\n */\nexport function createServerClient(options: NextZenmanageClientOptions): Zenmanage {\n let clients = clientsWithoutLogger;\n\n if (options.logger) {\n clients = clientsByLogger.get(options.logger) ?? new Map<string, Zenmanage>();\n clientsByLogger.set(options.logger, clients);\n }\n\n const key = configurationKey(options);\n let client = clients.get(key);\n\n if (!client) {\n client = buildClient(options);\n clients.set(key, client);\n }\n\n return client;\n}\n\n/** Forgets every shared client, and with them their cached rules. Mostly useful in tests. */\nexport function clearServerClients(): void {\n clientsWithoutLogger.clear();\n clientsByLogger = new WeakMap();\n}\n\nexport function createServerRuntime(options: ServerFlagsInput): NextFlagsRuntime {\n const client = createServerClient(options);\n\n // A manager loads its rules once and keeps them, whatever the cache TTL. Handing out the\n // client's own manager would pin the first rules it saw for the life of the process, so\n // every call gets a copy that reads the shared cache (and so honors the TTL) instead.\n let manager = client.flags().withDefaults(options.defaults ?? new DefaultsCollection());\n\n if (options.context) {\n manager = manager.withContext(options.context);\n }\n\n return { client, manager };\n}\n\nexport function createServerManager(options: ServerFlagsInput): FlagManager {\n return createServerRuntime(options).manager;\n}\n\nexport async function getServerFlag(input: ServerFlagInput): Promise<Flag> {\n const manager = createServerManager(input);\n return manager.single(input.key, input.defaultValue);\n}\n\nexport async function getServerFlags(input: ServerFlagsInput): Promise<Flag[]> {\n const manager = createServerManager(input);\n return manager.all();\n}\n\nexport async function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManager> {\n const manager = createServerManager(input);\n await manager.all();\n return manager;\n}\n\n/**\n * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or\n * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the\n * widened type (`string`, `boolean`), so the result can be compared with other values.\n *\n * `fallback` is also the default, for a flag that doesn't exist or when the API can't be\n * reached. A `defaultValue` in `input`, or an entry for the key in `input.defaults`, is more\n * specific and wins over it.\n */\nexport async function getFlagValue<T extends FlagValue>(\n input: ServerFlagInput,\n fallback: T\n): Promise<WidenFlagValue<T>> {\n // The SDK prefers an inline default over a defaults collection, so the fallback only goes in\n // when nothing else would supply one. Otherwise it would override the collection's entry.\n const hasOwnDefault = input.defaultValue !== undefined || input.defaults?.has(input.key) === true;\n const flag = await getServerFlag(hasOwnDefault ? input : { ...input, defaultValue: fallback });\n\n if (typeof fallback === 'boolean') {\n return flag.asBool() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'number') {\n return flag.asNumber() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'string') {\n return flag.asString() as WidenFlagValue<T>;\n }\n\n return flag.asJson() as WidenFlagValue<T>;\n}\n\n/**\n * Evaluates flags on the server and packages them for `NextFlagsProvider`. Pass `keys` to\n * send only the flags client components read.\n */\nexport async function getBootstrapPayload(input: ServerBootstrapInput): Promise<BootstrapPayload> {\n const flags = await getServerFlags(input);\n const wanted = input.keys ? new Set(input.keys) : null;\n\n return buildBootstrapPayload(\n flags.filter((flag) => wanted === null || wanted.has(flag.getKey())),\n { context: input.includeContext ? input.context : undefined }\n );\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.1\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n"],"mappings":"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","names":["DefaultsCollection","DefaultsCollection","DefaultsCollection"]}
package/dist/client.cjs CHANGED
@@ -136,7 +136,7 @@ function BootstrapProvider({ flags, children }) {
136
136
  var import_sdk3 = require("@zenmanage/sdk");
137
137
 
138
138
  // package.json
139
- var version = "1.0.0";
139
+ var version = "1.0.1";
140
140
 
141
141
  // src/identity.ts
142
142
  var CLIENT_AGENT = "zenmanage-nextjs";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/client/NextFlagsProvider.tsx","../src/bootstrap.ts","../src/types.ts","../src/defaults.ts","../src/client/BootstrapProvider.tsx","../src/client/context.ts","../src/client/createClient.ts","../package.json","../src/identity.ts","../src/client/hooks.ts"],"sourcesContent":["export { NextFlagsProvider } from './client/NextFlagsProvider';\nexport { useFlag, useVariant } from './client/hooks';\nexport { useFlagsContext } from '@zenmanage/react';\nexport type { NextFlagsProviderProps } from './types';\n","import { useCallback, useMemo, useRef, useSyncExternalStore, type ReactElement } from 'react';\nimport { FlagsProvider } from '@zenmanage/react';\nimport {\n contextFromBootstrapPayload,\n defaultsFromBootstrapPayload,\n deserializeBootstrapPayload,\n} from '../bootstrap';\nimport { mergeDefaults } from '../defaults';\nimport {\n DEFAULT_BOOTSTRAP_ELEMENT_ID,\n type BootstrapPayload,\n type NextFlagsProviderProps,\n} from '../types';\nimport { BootstrapProvider } from './BootstrapProvider';\nimport { createProviderClient } from './createClient';\n\nconst subscribeToNothing = (): (() => void) => () => undefined;\n\n/**\n * Reads the payload from a `<script>` tag. `useSyncExternalStore` serves `null` while\n * hydrating and the real payload right after, so the first client render matches the server\n * render instead of reading the DOM mid-render.\n */\nfunction useDocumentPayload(elementId: string, enabled: boolean): BootstrapPayload | null {\n const cache = useRef<{ text: string | null; payload: BootstrapPayload | null }>({\n text: null,\n payload: null,\n });\n\n const getSnapshot = useCallback((): BootstrapPayload | null => {\n if (!enabled || typeof document === 'undefined') {\n return null;\n }\n\n const text = document.getElementById(elementId)?.textContent ?? null;\n if (text !== cache.current.text) {\n cache.current = { text, payload: text === null ? null : deserializeBootstrapPayload(text) };\n }\n\n return cache.current.payload;\n }, [elementId, enabled]);\n\n return useSyncExternalStore(subscribeToNothing, getSnapshot, () => null);\n}\n\nexport function NextFlagsProvider({\n client,\n environmentToken,\n apiEndpoint,\n cacheTtl,\n enableUsageReporting,\n context,\n defaults,\n bootstrapPayload,\n bootstrapElementId = DEFAULT_BOOTSTRAP_ELEMENT_ID,\n children,\n ...providerProps\n}: NextFlagsProviderProps): ReactElement {\n const documentPayload = useDocumentPayload(bootstrapElementId, !bootstrapPayload);\n const payload = bootstrapPayload ?? documentPayload;\n\n const resolvedClient = useMemo(\n () =>\n client ??\n createProviderClient({ environmentToken, apiEndpoint, cacheTtl, enableUsageReporting }),\n [client, environmentToken, apiEndpoint, cacheTtl, enableUsageReporting]\n );\n\n const mergedDefaults = useMemo(\n () => mergeDefaults(defaults, defaultsFromBootstrapPayload(payload)),\n [defaults, payload]\n );\n\n // Without an explicit context the browser evaluates for the user the server evaluated for.\n const resolvedContext = useMemo(\n () => context ?? contextFromBootstrapPayload(payload),\n [context, payload]\n );\n\n return (\n <FlagsProvider\n {...providerProps}\n client={resolvedClient}\n context={resolvedContext}\n defaults={mergedDefaults}\n >\n <BootstrapProvider flags={payload?.flags ?? null}>{children}</BootstrapProvider>\n </FlagsProvider>\n );\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n","import { useEffect, useMemo, useState, type ReactElement, type ReactNode } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\nimport { useFlagsContext } from '@zenmanage/react';\nimport { BootstrapContext } from './context';\n\ninterface BootstrapProviderProps {\n flags: Record<string, FlagValue> | null;\n children: ReactNode;\n}\n\n/**\n * Sits inside `FlagsProvider`. Once the browser has tried to load the rules it records which\n * flags they contain, so `useFlag` can tell \"the live value\" from \"the SDK fell back to the\n * default because nothing loaded\".\n */\nexport function BootstrapProvider({ flags, children }: BootstrapProviderProps): ReactElement {\n const { manager, isReady, revision } = useFlagsContext();\n const [live, setLive] = useState<ReadonlySet<string> | null>(null);\n\n useEffect(() => {\n if (!flags || !isReady) {\n return undefined;\n }\n\n let isCurrent = true;\n\n // The SDK serves defaults instead of throwing when rules can't be loaded, so an empty list\n // is what an unreachable API looks like.\n manager.all().then(\n (loaded) => isCurrent && setLive(new Set(loaded.map((flag) => flag.getKey()))),\n () => isCurrent && setLive(new Set())\n );\n\n return () => {\n isCurrent = false;\n };\n }, [flags, isReady, manager, revision]);\n\n const value = useMemo(() => (flags ? { flags, live } : null), [flags, live]);\n\n return <BootstrapContext.Provider value={value}>{children}</BootstrapContext.Provider>;\n}\n","import { createContext } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\n\nexport interface BootstrapState {\n /** The flag values the server evaluated. */\n flags: Record<string, FlagValue>;\n /**\n * The keys the browser's own rules contain, or `null` until it has asked. A flag that is\n * bootstrapped but absent here has no live evaluation behind it (the rules didn't load, or\n * the flag doesn't exist), so the server's value is the best answer there is.\n */\n live: ReadonlySet<string> | null;\n}\n\n/** `null` when the provider wasn't given a bootstrap payload. */\nexport const BootstrapContext = createContext<BootstrapState | null>(null);\n","import { ConfigBuilder, Zenmanage } from '@zenmanage/sdk';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from '../identity';\n\ninterface ClientOptions {\n environmentToken?: string;\n apiEndpoint?: string;\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n}\n\n// Mirrors how @zenmanage/sdk tells the runtimes apart.\nfunction isServerRender(): boolean {\n return typeof window === 'undefined' && typeof document === 'undefined';\n}\n\n/**\n * Builds the client a provider hands to its hooks.\n *\n * Next.js renders Client Components on the server as well as in the browser, and the SDK\n * refuses a client key in Node.js. Nothing is fetched while rendering (effects don't run), so\n * the server render gets a client assembled without that check, which is only ever asked for\n * its defaults. The browser gets a fully validated one.\n */\nexport function createProviderClient(options: ClientOptions): Zenmanage {\n const { environmentToken, apiEndpoint, cacheTtl, enableUsageReporting } = options;\n\n if (!environmentToken) {\n throw new Error('NextFlagsProvider requires either a client or an environmentToken.');\n }\n\n // A prop on a Client Component is written into the page, so a server key here would leak.\n if (environmentToken.startsWith('srv_')) {\n throw new Error(\n 'NextFlagsProvider received a server key (srv_...). Server keys must never reach the browser; use a client key (cli_...).'\n );\n }\n\n if (isServerRender()) {\n if (!environmentToken.startsWith('cli_')) {\n throw new Error('NextFlagsProvider needs a client key (cli_...).');\n }\n\n return new Zenmanage({\n environmentToken,\n apiEndpoint,\n cacheTtl,\n enableUsageReporting,\n clientAgent: CLIENT_AGENT,\n sdkVersion: PACKAGE_VERSION,\n });\n }\n\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION);\n\n if (apiEndpoint) {\n builder.withApiEndpoint(apiEndpoint);\n }\n\n if (typeof cacheTtl === 'number') {\n builder.withCacheTtl(cacheTtl);\n }\n\n if (typeof enableUsageReporting === 'boolean') {\n builder.withUsageReporting(enableUsageReporting);\n }\n\n return new Zenmanage(builder.build());\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.0\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { useContext } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\nimport {\n useFlag as useReactFlag,\n type UseFlagResult,\n type UseVariantResult,\n type WidenFlagValue,\n} from '@zenmanage/react';\nimport { BootstrapContext } from './context';\n\nfunction sameKind(candidate: FlagValue, fallback: FlagValue): boolean {\n if (typeof fallback === 'object') {\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n Array.isArray(candidate) === Array.isArray(fallback)\n );\n }\n\n return typeof candidate === typeof fallback;\n}\n\n/**\n * Like `useFlag` from `@zenmanage/react`, but it serves the value the server evaluated until\n * the browser has a live evaluation to replace it. Both the server render and the first client\n * render use that value, with `isLoading` false, so the page hydrates without a mismatch or a\n * flash of the default. Once the browser's rules load, the live value takes over. If they\n * never load (a blocked CDN, no network), the server's value stays rather than the page\n * switching to the code default.\n *\n * Without a bootstrap value for the key it behaves exactly like the React hook.\n */\nexport function useFlag<T extends FlagValue>(\n key: string,\n defaultValue: T\n): UseFlagResult<WidenFlagValue<T>> {\n const bootstrap = useContext(BootstrapContext);\n // The hook's own default stays the fallback for the live evaluation: reporting the server's\n // value as the code default would corrupt the default-value usage the API records.\n const result = useReactFlag(key, defaultValue);\n\n const seed = bootstrap?.flags[key];\n if (seed === undefined || !sameKind(seed, defaultValue)) {\n return result;\n }\n\n const hasLiveValue = bootstrap?.live?.has(key) === true;\n if (hasLiveValue && !result.isLoading) {\n return result;\n }\n\n return { ...result, value: seed as WidenFlagValue<T>, isLoading: false };\n}\n\nexport function useVariant(key: string, defaultVariant = 'control'): UseVariantResult {\n const result = useFlag<string>(key, defaultVariant);\n\n return {\n key,\n variant: result.value,\n flag: result.flag,\n isLoading: result.isLoading,\n error: result.error,\n refresh: result.refresh,\n };\n}\n"],"mappings":"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","names":["import_react","import_sdk","import_react","import_sdk","import_jsx_runtime","import_react","useReactFlag","import_react"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/client/NextFlagsProvider.tsx","../src/bootstrap.ts","../src/types.ts","../src/defaults.ts","../src/client/BootstrapProvider.tsx","../src/client/context.ts","../src/client/createClient.ts","../package.json","../src/identity.ts","../src/client/hooks.ts"],"sourcesContent":["export { NextFlagsProvider } from './client/NextFlagsProvider';\nexport { useFlag, useVariant } from './client/hooks';\nexport { useFlagsContext } from '@zenmanage/react';\nexport type { NextFlagsProviderProps } from './types';\n","import { useCallback, useMemo, useRef, useSyncExternalStore, type ReactElement } from 'react';\nimport { FlagsProvider } from '@zenmanage/react';\nimport {\n contextFromBootstrapPayload,\n defaultsFromBootstrapPayload,\n deserializeBootstrapPayload,\n} from '../bootstrap';\nimport { mergeDefaults } from '../defaults';\nimport {\n DEFAULT_BOOTSTRAP_ELEMENT_ID,\n type BootstrapPayload,\n type NextFlagsProviderProps,\n} from '../types';\nimport { BootstrapProvider } from './BootstrapProvider';\nimport { createProviderClient } from './createClient';\n\nconst subscribeToNothing = (): (() => void) => () => undefined;\n\n/**\n * Reads the payload from a `<script>` tag. `useSyncExternalStore` serves `null` while\n * hydrating and the real payload right after, so the first client render matches the server\n * render instead of reading the DOM mid-render.\n */\nfunction useDocumentPayload(elementId: string, enabled: boolean): BootstrapPayload | null {\n const cache = useRef<{ text: string | null; payload: BootstrapPayload | null }>({\n text: null,\n payload: null,\n });\n\n const getSnapshot = useCallback((): BootstrapPayload | null => {\n if (!enabled || typeof document === 'undefined') {\n return null;\n }\n\n const text = document.getElementById(elementId)?.textContent ?? null;\n if (text !== cache.current.text) {\n cache.current = { text, payload: text === null ? null : deserializeBootstrapPayload(text) };\n }\n\n return cache.current.payload;\n }, [elementId, enabled]);\n\n return useSyncExternalStore(subscribeToNothing, getSnapshot, () => null);\n}\n\nexport function NextFlagsProvider({\n client,\n environmentToken,\n apiEndpoint,\n cacheTtl,\n enableUsageReporting,\n context,\n defaults,\n bootstrapPayload,\n bootstrapElementId = DEFAULT_BOOTSTRAP_ELEMENT_ID,\n children,\n ...providerProps\n}: NextFlagsProviderProps): ReactElement {\n const documentPayload = useDocumentPayload(bootstrapElementId, !bootstrapPayload);\n const payload = bootstrapPayload ?? documentPayload;\n\n const resolvedClient = useMemo(\n () =>\n client ??\n createProviderClient({ environmentToken, apiEndpoint, cacheTtl, enableUsageReporting }),\n [client, environmentToken, apiEndpoint, cacheTtl, enableUsageReporting]\n );\n\n const mergedDefaults = useMemo(\n () => mergeDefaults(defaults, defaultsFromBootstrapPayload(payload)),\n [defaults, payload]\n );\n\n // Without an explicit context the browser evaluates for the user the server evaluated for.\n const resolvedContext = useMemo(\n () => context ?? contextFromBootstrapPayload(payload),\n [context, payload]\n );\n\n return (\n <FlagsProvider\n {...providerProps}\n client={resolvedClient}\n context={resolvedContext}\n defaults={mergedDefaults}\n >\n <BootstrapProvider flags={payload?.flags ?? null}>{children}</BootstrapProvider>\n </FlagsProvider>\n );\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n","import { useEffect, useMemo, useState, type ReactElement, type ReactNode } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\nimport { useFlagsContext } from '@zenmanage/react';\nimport { BootstrapContext } from './context';\n\ninterface BootstrapProviderProps {\n flags: Record<string, FlagValue> | null;\n children: ReactNode;\n}\n\n/**\n * Sits inside `FlagsProvider`. Once the browser has tried to load the rules it records which\n * flags they contain, so `useFlag` can tell \"the live value\" from \"the SDK fell back to the\n * default because nothing loaded\".\n */\nexport function BootstrapProvider({ flags, children }: BootstrapProviderProps): ReactElement {\n const { manager, isReady, revision } = useFlagsContext();\n const [live, setLive] = useState<ReadonlySet<string> | null>(null);\n\n useEffect(() => {\n if (!flags || !isReady) {\n return undefined;\n }\n\n let isCurrent = true;\n\n // The SDK serves defaults instead of throwing when rules can't be loaded, so an empty list\n // is what an unreachable API looks like.\n manager.all().then(\n (loaded) => isCurrent && setLive(new Set(loaded.map((flag) => flag.getKey()))),\n () => isCurrent && setLive(new Set())\n );\n\n return () => {\n isCurrent = false;\n };\n }, [flags, isReady, manager, revision]);\n\n const value = useMemo(() => (flags ? { flags, live } : null), [flags, live]);\n\n return <BootstrapContext.Provider value={value}>{children}</BootstrapContext.Provider>;\n}\n","import { createContext } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\n\nexport interface BootstrapState {\n /** The flag values the server evaluated. */\n flags: Record<string, FlagValue>;\n /**\n * The keys the browser's own rules contain, or `null` until it has asked. A flag that is\n * bootstrapped but absent here has no live evaluation behind it (the rules didn't load, or\n * the flag doesn't exist), so the server's value is the best answer there is.\n */\n live: ReadonlySet<string> | null;\n}\n\n/** `null` when the provider wasn't given a bootstrap payload. */\nexport const BootstrapContext = createContext<BootstrapState | null>(null);\n","import { ConfigBuilder, Zenmanage } from '@zenmanage/sdk';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from '../identity';\n\ninterface ClientOptions {\n environmentToken?: string;\n apiEndpoint?: string;\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n}\n\n// Mirrors how @zenmanage/sdk tells the runtimes apart.\nfunction isServerRender(): boolean {\n return typeof window === 'undefined' && typeof document === 'undefined';\n}\n\n/**\n * Builds the client a provider hands to its hooks.\n *\n * Next.js renders Client Components on the server as well as in the browser, and the SDK\n * refuses a client key in Node.js. Nothing is fetched while rendering (effects don't run), so\n * the server render gets a client assembled without that check, which is only ever asked for\n * its defaults. The browser gets a fully validated one.\n */\nexport function createProviderClient(options: ClientOptions): Zenmanage {\n const { environmentToken, apiEndpoint, cacheTtl, enableUsageReporting } = options;\n\n if (!environmentToken) {\n throw new Error('NextFlagsProvider requires either a client or an environmentToken.');\n }\n\n // A prop on a Client Component is written into the page, so a server key here would leak.\n if (environmentToken.startsWith('srv_')) {\n throw new Error(\n 'NextFlagsProvider received a server key (srv_...). Server keys must never reach the browser; use a client key (cli_...).'\n );\n }\n\n if (isServerRender()) {\n if (!environmentToken.startsWith('cli_')) {\n throw new Error('NextFlagsProvider needs a client key (cli_...).');\n }\n\n return new Zenmanage({\n environmentToken,\n apiEndpoint,\n cacheTtl,\n enableUsageReporting,\n clientAgent: CLIENT_AGENT,\n sdkVersion: PACKAGE_VERSION,\n });\n }\n\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION);\n\n if (apiEndpoint) {\n builder.withApiEndpoint(apiEndpoint);\n }\n\n if (typeof cacheTtl === 'number') {\n builder.withCacheTtl(cacheTtl);\n }\n\n if (typeof enableUsageReporting === 'boolean') {\n builder.withUsageReporting(enableUsageReporting);\n }\n\n return new Zenmanage(builder.build());\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.1\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { useContext } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\nimport {\n useFlag as useReactFlag,\n type UseFlagResult,\n type UseVariantResult,\n type WidenFlagValue,\n} from '@zenmanage/react';\nimport { BootstrapContext } from './context';\n\nfunction sameKind(candidate: FlagValue, fallback: FlagValue): boolean {\n if (typeof fallback === 'object') {\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n Array.isArray(candidate) === Array.isArray(fallback)\n );\n }\n\n return typeof candidate === typeof fallback;\n}\n\n/**\n * Like `useFlag` from `@zenmanage/react`, but it serves the value the server evaluated until\n * the browser has a live evaluation to replace it. Both the server render and the first client\n * render use that value, with `isLoading` false, so the page hydrates without a mismatch or a\n * flash of the default. Once the browser's rules load, the live value takes over. If they\n * never load (a blocked CDN, no network), the server's value stays rather than the page\n * switching to the code default.\n *\n * Without a bootstrap value for the key it behaves exactly like the React hook.\n */\nexport function useFlag<T extends FlagValue>(\n key: string,\n defaultValue: T\n): UseFlagResult<WidenFlagValue<T>> {\n const bootstrap = useContext(BootstrapContext);\n // The hook's own default stays the fallback for the live evaluation: reporting the server's\n // value as the code default would corrupt the default-value usage the API records.\n const result = useReactFlag(key, defaultValue);\n\n const seed = bootstrap?.flags[key];\n if (seed === undefined || !sameKind(seed, defaultValue)) {\n return result;\n }\n\n const hasLiveValue = bootstrap?.live?.has(key) === true;\n if (hasLiveValue && !result.isLoading) {\n return result;\n }\n\n return { ...result, value: seed as WidenFlagValue<T>, isLoading: false };\n}\n\nexport function useVariant(key: string, defaultVariant = 'control'): UseVariantResult {\n const result = useFlag<string>(key, defaultVariant);\n\n return {\n key,\n variant: result.value,\n flag: result.flag,\n isLoading: result.isLoading,\n error: result.error,\n refresh: result.refresh,\n };\n}\n"],"mappings":"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","names":["import_react","import_sdk","import_react","import_sdk","import_jsx_runtime","import_react","useReactFlag","import_react"]}
package/dist/client.js CHANGED
@@ -111,7 +111,7 @@ function BootstrapProvider({ flags, children }) {
111
111
  import { ConfigBuilder, Zenmanage } from "@zenmanage/sdk";
112
112
 
113
113
  // package.json
114
- var version = "1.0.0";
114
+ var version = "1.0.1";
115
115
 
116
116
  // src/identity.ts
117
117
  var CLIENT_AGENT = "zenmanage-nextjs";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/NextFlagsProvider.tsx","../src/bootstrap.ts","../src/types.ts","../src/defaults.ts","../src/client/BootstrapProvider.tsx","../src/client/context.ts","../src/client/createClient.ts","../package.json","../src/identity.ts","../src/client/hooks.ts","../src/client.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useSyncExternalStore, type ReactElement } from 'react';\nimport { FlagsProvider } from '@zenmanage/react';\nimport {\n contextFromBootstrapPayload,\n defaultsFromBootstrapPayload,\n deserializeBootstrapPayload,\n} from '../bootstrap';\nimport { mergeDefaults } from '../defaults';\nimport {\n DEFAULT_BOOTSTRAP_ELEMENT_ID,\n type BootstrapPayload,\n type NextFlagsProviderProps,\n} from '../types';\nimport { BootstrapProvider } from './BootstrapProvider';\nimport { createProviderClient } from './createClient';\n\nconst subscribeToNothing = (): (() => void) => () => undefined;\n\n/**\n * Reads the payload from a `<script>` tag. `useSyncExternalStore` serves `null` while\n * hydrating and the real payload right after, so the first client render matches the server\n * render instead of reading the DOM mid-render.\n */\nfunction useDocumentPayload(elementId: string, enabled: boolean): BootstrapPayload | null {\n const cache = useRef<{ text: string | null; payload: BootstrapPayload | null }>({\n text: null,\n payload: null,\n });\n\n const getSnapshot = useCallback((): BootstrapPayload | null => {\n if (!enabled || typeof document === 'undefined') {\n return null;\n }\n\n const text = document.getElementById(elementId)?.textContent ?? null;\n if (text !== cache.current.text) {\n cache.current = { text, payload: text === null ? null : deserializeBootstrapPayload(text) };\n }\n\n return cache.current.payload;\n }, [elementId, enabled]);\n\n return useSyncExternalStore(subscribeToNothing, getSnapshot, () => null);\n}\n\nexport function NextFlagsProvider({\n client,\n environmentToken,\n apiEndpoint,\n cacheTtl,\n enableUsageReporting,\n context,\n defaults,\n bootstrapPayload,\n bootstrapElementId = DEFAULT_BOOTSTRAP_ELEMENT_ID,\n children,\n ...providerProps\n}: NextFlagsProviderProps): ReactElement {\n const documentPayload = useDocumentPayload(bootstrapElementId, !bootstrapPayload);\n const payload = bootstrapPayload ?? documentPayload;\n\n const resolvedClient = useMemo(\n () =>\n client ??\n createProviderClient({ environmentToken, apiEndpoint, cacheTtl, enableUsageReporting }),\n [client, environmentToken, apiEndpoint, cacheTtl, enableUsageReporting]\n );\n\n const mergedDefaults = useMemo(\n () => mergeDefaults(defaults, defaultsFromBootstrapPayload(payload)),\n [defaults, payload]\n );\n\n // Without an explicit context the browser evaluates for the user the server evaluated for.\n const resolvedContext = useMemo(\n () => context ?? contextFromBootstrapPayload(payload),\n [context, payload]\n );\n\n return (\n <FlagsProvider\n {...providerProps}\n client={resolvedClient}\n context={resolvedContext}\n defaults={mergedDefaults}\n >\n <BootstrapProvider flags={payload?.flags ?? null}>{children}</BootstrapProvider>\n </FlagsProvider>\n );\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n","import { useEffect, useMemo, useState, type ReactElement, type ReactNode } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\nimport { useFlagsContext } from '@zenmanage/react';\nimport { BootstrapContext } from './context';\n\ninterface BootstrapProviderProps {\n flags: Record<string, FlagValue> | null;\n children: ReactNode;\n}\n\n/**\n * Sits inside `FlagsProvider`. Once the browser has tried to load the rules it records which\n * flags they contain, so `useFlag` can tell \"the live value\" from \"the SDK fell back to the\n * default because nothing loaded\".\n */\nexport function BootstrapProvider({ flags, children }: BootstrapProviderProps): ReactElement {\n const { manager, isReady, revision } = useFlagsContext();\n const [live, setLive] = useState<ReadonlySet<string> | null>(null);\n\n useEffect(() => {\n if (!flags || !isReady) {\n return undefined;\n }\n\n let isCurrent = true;\n\n // The SDK serves defaults instead of throwing when rules can't be loaded, so an empty list\n // is what an unreachable API looks like.\n manager.all().then(\n (loaded) => isCurrent && setLive(new Set(loaded.map((flag) => flag.getKey()))),\n () => isCurrent && setLive(new Set())\n );\n\n return () => {\n isCurrent = false;\n };\n }, [flags, isReady, manager, revision]);\n\n const value = useMemo(() => (flags ? { flags, live } : null), [flags, live]);\n\n return <BootstrapContext.Provider value={value}>{children}</BootstrapContext.Provider>;\n}\n","import { createContext } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\n\nexport interface BootstrapState {\n /** The flag values the server evaluated. */\n flags: Record<string, FlagValue>;\n /**\n * The keys the browser's own rules contain, or `null` until it has asked. A flag that is\n * bootstrapped but absent here has no live evaluation behind it (the rules didn't load, or\n * the flag doesn't exist), so the server's value is the best answer there is.\n */\n live: ReadonlySet<string> | null;\n}\n\n/** `null` when the provider wasn't given a bootstrap payload. */\nexport const BootstrapContext = createContext<BootstrapState | null>(null);\n","import { ConfigBuilder, Zenmanage } from '@zenmanage/sdk';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from '../identity';\n\ninterface ClientOptions {\n environmentToken?: string;\n apiEndpoint?: string;\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n}\n\n// Mirrors how @zenmanage/sdk tells the runtimes apart.\nfunction isServerRender(): boolean {\n return typeof window === 'undefined' && typeof document === 'undefined';\n}\n\n/**\n * Builds the client a provider hands to its hooks.\n *\n * Next.js renders Client Components on the server as well as in the browser, and the SDK\n * refuses a client key in Node.js. Nothing is fetched while rendering (effects don't run), so\n * the server render gets a client assembled without that check, which is only ever asked for\n * its defaults. The browser gets a fully validated one.\n */\nexport function createProviderClient(options: ClientOptions): Zenmanage {\n const { environmentToken, apiEndpoint, cacheTtl, enableUsageReporting } = options;\n\n if (!environmentToken) {\n throw new Error('NextFlagsProvider requires either a client or an environmentToken.');\n }\n\n // A prop on a Client Component is written into the page, so a server key here would leak.\n if (environmentToken.startsWith('srv_')) {\n throw new Error(\n 'NextFlagsProvider received a server key (srv_...). Server keys must never reach the browser; use a client key (cli_...).'\n );\n }\n\n if (isServerRender()) {\n if (!environmentToken.startsWith('cli_')) {\n throw new Error('NextFlagsProvider needs a client key (cli_...).');\n }\n\n return new Zenmanage({\n environmentToken,\n apiEndpoint,\n cacheTtl,\n enableUsageReporting,\n clientAgent: CLIENT_AGENT,\n sdkVersion: PACKAGE_VERSION,\n });\n }\n\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION);\n\n if (apiEndpoint) {\n builder.withApiEndpoint(apiEndpoint);\n }\n\n if (typeof cacheTtl === 'number') {\n builder.withCacheTtl(cacheTtl);\n }\n\n if (typeof enableUsageReporting === 'boolean') {\n builder.withUsageReporting(enableUsageReporting);\n }\n\n return new Zenmanage(builder.build());\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.0\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { useContext } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\nimport {\n useFlag as useReactFlag,\n type UseFlagResult,\n type UseVariantResult,\n type WidenFlagValue,\n} from '@zenmanage/react';\nimport { BootstrapContext } from './context';\n\nfunction sameKind(candidate: FlagValue, fallback: FlagValue): boolean {\n if (typeof fallback === 'object') {\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n Array.isArray(candidate) === Array.isArray(fallback)\n );\n }\n\n return typeof candidate === typeof fallback;\n}\n\n/**\n * Like `useFlag` from `@zenmanage/react`, but it serves the value the server evaluated until\n * the browser has a live evaluation to replace it. Both the server render and the first client\n * render use that value, with `isLoading` false, so the page hydrates without a mismatch or a\n * flash of the default. Once the browser's rules load, the live value takes over. If they\n * never load (a blocked CDN, no network), the server's value stays rather than the page\n * switching to the code default.\n *\n * Without a bootstrap value for the key it behaves exactly like the React hook.\n */\nexport function useFlag<T extends FlagValue>(\n key: string,\n defaultValue: T\n): UseFlagResult<WidenFlagValue<T>> {\n const bootstrap = useContext(BootstrapContext);\n // The hook's own default stays the fallback for the live evaluation: reporting the server's\n // value as the code default would corrupt the default-value usage the API records.\n const result = useReactFlag(key, defaultValue);\n\n const seed = bootstrap?.flags[key];\n if (seed === undefined || !sameKind(seed, defaultValue)) {\n return result;\n }\n\n const hasLiveValue = bootstrap?.live?.has(key) === true;\n if (hasLiveValue && !result.isLoading) {\n return result;\n }\n\n return { ...result, value: seed as WidenFlagValue<T>, isLoading: false };\n}\n\nexport function useVariant(key: string, defaultVariant = 'control'): UseVariantResult {\n const result = useFlag<string>(key, defaultVariant);\n\n return {\n key,\n variant: result.value,\n flag: result.flag,\n isLoading: result.isLoading,\n error: result.error,\n refresh: result.refresh,\n };\n}\n","export { NextFlagsProvider } from './client/NextFlagsProvider';\nexport { useFlag, useVariant } from './client/hooks';\nexport { useFlagsContext } from '@zenmanage/react';\nexport type { NextFlagsProviderProps } from './types';\n"],"mappings":"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","names":["useMemo","DefaultsCollection","jsx","useMemo","useFlagsContext"]}
1
+ {"version":3,"sources":["../src/client/NextFlagsProvider.tsx","../src/bootstrap.ts","../src/types.ts","../src/defaults.ts","../src/client/BootstrapProvider.tsx","../src/client/context.ts","../src/client/createClient.ts","../package.json","../src/identity.ts","../src/client/hooks.ts","../src/client.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useSyncExternalStore, type ReactElement } from 'react';\nimport { FlagsProvider } from '@zenmanage/react';\nimport {\n contextFromBootstrapPayload,\n defaultsFromBootstrapPayload,\n deserializeBootstrapPayload,\n} from '../bootstrap';\nimport { mergeDefaults } from '../defaults';\nimport {\n DEFAULT_BOOTSTRAP_ELEMENT_ID,\n type BootstrapPayload,\n type NextFlagsProviderProps,\n} from '../types';\nimport { BootstrapProvider } from './BootstrapProvider';\nimport { createProviderClient } from './createClient';\n\nconst subscribeToNothing = (): (() => void) => () => undefined;\n\n/**\n * Reads the payload from a `<script>` tag. `useSyncExternalStore` serves `null` while\n * hydrating and the real payload right after, so the first client render matches the server\n * render instead of reading the DOM mid-render.\n */\nfunction useDocumentPayload(elementId: string, enabled: boolean): BootstrapPayload | null {\n const cache = useRef<{ text: string | null; payload: BootstrapPayload | null }>({\n text: null,\n payload: null,\n });\n\n const getSnapshot = useCallback((): BootstrapPayload | null => {\n if (!enabled || typeof document === 'undefined') {\n return null;\n }\n\n const text = document.getElementById(elementId)?.textContent ?? null;\n if (text !== cache.current.text) {\n cache.current = { text, payload: text === null ? null : deserializeBootstrapPayload(text) };\n }\n\n return cache.current.payload;\n }, [elementId, enabled]);\n\n return useSyncExternalStore(subscribeToNothing, getSnapshot, () => null);\n}\n\nexport function NextFlagsProvider({\n client,\n environmentToken,\n apiEndpoint,\n cacheTtl,\n enableUsageReporting,\n context,\n defaults,\n bootstrapPayload,\n bootstrapElementId = DEFAULT_BOOTSTRAP_ELEMENT_ID,\n children,\n ...providerProps\n}: NextFlagsProviderProps): ReactElement {\n const documentPayload = useDocumentPayload(bootstrapElementId, !bootstrapPayload);\n const payload = bootstrapPayload ?? documentPayload;\n\n const resolvedClient = useMemo(\n () =>\n client ??\n createProviderClient({ environmentToken, apiEndpoint, cacheTtl, enableUsageReporting }),\n [client, environmentToken, apiEndpoint, cacheTtl, enableUsageReporting]\n );\n\n const mergedDefaults = useMemo(\n () => mergeDefaults(defaults, defaultsFromBootstrapPayload(payload)),\n [defaults, payload]\n );\n\n // Without an explicit context the browser evaluates for the user the server evaluated for.\n const resolvedContext = useMemo(\n () => context ?? contextFromBootstrapPayload(payload),\n [context, payload]\n );\n\n return (\n <FlagsProvider\n {...providerProps}\n client={resolvedClient}\n context={resolvedContext}\n defaults={mergedDefaults}\n >\n <BootstrapProvider flags={payload?.flags ?? null}>{children}</BootstrapProvider>\n </FlagsProvider>\n );\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n","import { useEffect, useMemo, useState, type ReactElement, type ReactNode } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\nimport { useFlagsContext } from '@zenmanage/react';\nimport { BootstrapContext } from './context';\n\ninterface BootstrapProviderProps {\n flags: Record<string, FlagValue> | null;\n children: ReactNode;\n}\n\n/**\n * Sits inside `FlagsProvider`. Once the browser has tried to load the rules it records which\n * flags they contain, so `useFlag` can tell \"the live value\" from \"the SDK fell back to the\n * default because nothing loaded\".\n */\nexport function BootstrapProvider({ flags, children }: BootstrapProviderProps): ReactElement {\n const { manager, isReady, revision } = useFlagsContext();\n const [live, setLive] = useState<ReadonlySet<string> | null>(null);\n\n useEffect(() => {\n if (!flags || !isReady) {\n return undefined;\n }\n\n let isCurrent = true;\n\n // The SDK serves defaults instead of throwing when rules can't be loaded, so an empty list\n // is what an unreachable API looks like.\n manager.all().then(\n (loaded) => isCurrent && setLive(new Set(loaded.map((flag) => flag.getKey()))),\n () => isCurrent && setLive(new Set())\n );\n\n return () => {\n isCurrent = false;\n };\n }, [flags, isReady, manager, revision]);\n\n const value = useMemo(() => (flags ? { flags, live } : null), [flags, live]);\n\n return <BootstrapContext.Provider value={value}>{children}</BootstrapContext.Provider>;\n}\n","import { createContext } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\n\nexport interface BootstrapState {\n /** The flag values the server evaluated. */\n flags: Record<string, FlagValue>;\n /**\n * The keys the browser's own rules contain, or `null` until it has asked. A flag that is\n * bootstrapped but absent here has no live evaluation behind it (the rules didn't load, or\n * the flag doesn't exist), so the server's value is the best answer there is.\n */\n live: ReadonlySet<string> | null;\n}\n\n/** `null` when the provider wasn't given a bootstrap payload. */\nexport const BootstrapContext = createContext<BootstrapState | null>(null);\n","import { ConfigBuilder, Zenmanage } from '@zenmanage/sdk';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from '../identity';\n\ninterface ClientOptions {\n environmentToken?: string;\n apiEndpoint?: string;\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n}\n\n// Mirrors how @zenmanage/sdk tells the runtimes apart.\nfunction isServerRender(): boolean {\n return typeof window === 'undefined' && typeof document === 'undefined';\n}\n\n/**\n * Builds the client a provider hands to its hooks.\n *\n * Next.js renders Client Components on the server as well as in the browser, and the SDK\n * refuses a client key in Node.js. Nothing is fetched while rendering (effects don't run), so\n * the server render gets a client assembled without that check, which is only ever asked for\n * its defaults. The browser gets a fully validated one.\n */\nexport function createProviderClient(options: ClientOptions): Zenmanage {\n const { environmentToken, apiEndpoint, cacheTtl, enableUsageReporting } = options;\n\n if (!environmentToken) {\n throw new Error('NextFlagsProvider requires either a client or an environmentToken.');\n }\n\n // A prop on a Client Component is written into the page, so a server key here would leak.\n if (environmentToken.startsWith('srv_')) {\n throw new Error(\n 'NextFlagsProvider received a server key (srv_...). Server keys must never reach the browser; use a client key (cli_...).'\n );\n }\n\n if (isServerRender()) {\n if (!environmentToken.startsWith('cli_')) {\n throw new Error('NextFlagsProvider needs a client key (cli_...).');\n }\n\n return new Zenmanage({\n environmentToken,\n apiEndpoint,\n cacheTtl,\n enableUsageReporting,\n clientAgent: CLIENT_AGENT,\n sdkVersion: PACKAGE_VERSION,\n });\n }\n\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION);\n\n if (apiEndpoint) {\n builder.withApiEndpoint(apiEndpoint);\n }\n\n if (typeof cacheTtl === 'number') {\n builder.withCacheTtl(cacheTtl);\n }\n\n if (typeof enableUsageReporting === 'boolean') {\n builder.withUsageReporting(enableUsageReporting);\n }\n\n return new Zenmanage(builder.build());\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.1\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { useContext } from 'react';\nimport type { FlagValue } from '@zenmanage/sdk';\nimport {\n useFlag as useReactFlag,\n type UseFlagResult,\n type UseVariantResult,\n type WidenFlagValue,\n} from '@zenmanage/react';\nimport { BootstrapContext } from './context';\n\nfunction sameKind(candidate: FlagValue, fallback: FlagValue): boolean {\n if (typeof fallback === 'object') {\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n Array.isArray(candidate) === Array.isArray(fallback)\n );\n }\n\n return typeof candidate === typeof fallback;\n}\n\n/**\n * Like `useFlag` from `@zenmanage/react`, but it serves the value the server evaluated until\n * the browser has a live evaluation to replace it. Both the server render and the first client\n * render use that value, with `isLoading` false, so the page hydrates without a mismatch or a\n * flash of the default. Once the browser's rules load, the live value takes over. If they\n * never load (a blocked CDN, no network), the server's value stays rather than the page\n * switching to the code default.\n *\n * Without a bootstrap value for the key it behaves exactly like the React hook.\n */\nexport function useFlag<T extends FlagValue>(\n key: string,\n defaultValue: T\n): UseFlagResult<WidenFlagValue<T>> {\n const bootstrap = useContext(BootstrapContext);\n // The hook's own default stays the fallback for the live evaluation: reporting the server's\n // value as the code default would corrupt the default-value usage the API records.\n const result = useReactFlag(key, defaultValue);\n\n const seed = bootstrap?.flags[key];\n if (seed === undefined || !sameKind(seed, defaultValue)) {\n return result;\n }\n\n const hasLiveValue = bootstrap?.live?.has(key) === true;\n if (hasLiveValue && !result.isLoading) {\n return result;\n }\n\n return { ...result, value: seed as WidenFlagValue<T>, isLoading: false };\n}\n\nexport function useVariant(key: string, defaultVariant = 'control'): UseVariantResult {\n const result = useFlag<string>(key, defaultVariant);\n\n return {\n key,\n variant: result.value,\n flag: result.flag,\n isLoading: result.isLoading,\n error: result.error,\n refresh: result.refresh,\n };\n}\n","export { NextFlagsProvider } from './client/NextFlagsProvider';\nexport { useFlag, useVariant } from './client/hooks';\nexport { useFlagsContext } from '@zenmanage/react';\nexport type { NextFlagsProviderProps } from './types';\n"],"mappings":"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","names":["useMemo","DefaultsCollection","jsx","useMemo","useFlagsContext"]}
package/dist/index.cjs CHANGED
@@ -156,7 +156,7 @@ function mergeDefaults(base, bootstrap) {
156
156
  var import_sdk3 = require("@zenmanage/sdk");
157
157
 
158
158
  // package.json
159
- var version = "1.0.0";
159
+ var version = "1.0.1";
160
160
 
161
161
  // src/identity.ts
162
162
  var CLIENT_AGENT = "zenmanage-nextjs";
@@ -229,7 +229,8 @@ async function preloadServerFlags(input) {
229
229
  return manager;
230
230
  }
231
231
  async function getFlagValue(input, fallback) {
232
- const flag = await getServerFlag(input);
232
+ const hasOwnDefault = input.defaultValue !== void 0 || input.defaults?.has(input.key) === true;
233
+ const flag = await getServerFlag(hasOwnDefault ? input : { ...input, defaultValue: fallback });
233
234
  if (typeof fallback === "boolean") {
234
235
  return flag.asBool();
235
236
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/bootstrap.ts","../src/defaults.ts","../src/server.ts","../package.json","../src/identity.ts","../src/middleware.ts"],"sourcesContent":["export {\n DEFAULT_BOOTSTRAP_ELEMENT_ID,\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type BuildBootstrapPayloadOptions,\n type BuildBootstrapScriptOptions,\n type BuildRequestContextOptions,\n type MiddlewareFlagInput,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type RequestCookiesLike,\n type RequestGeoLike,\n type RequestLike,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport {\n buildBootstrapPayload,\n buildBootstrapScript,\n contextFromBootstrapPayload,\n defaultsFromBootstrapDocument,\n defaultsFromBootstrapPayload,\n deserializeBootstrapPayload,\n readBootstrapPayloadFromDocument,\n serializeBootstrapPayload,\n} from './bootstrap';\n\nexport { mergeDefaults } from './defaults';\n\nexport {\n clearServerClients,\n createServerClient,\n createServerManager,\n createServerRuntime,\n getBootstrapPayload,\n getFlagValue,\n getServerFlag,\n getServerFlags,\n preloadServerFlags,\n} from './server';\n\nexport { buildContextFromRequest, evaluateMiddlewareFlag } from './middleware';\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n","import {\n ConfigBuilder,\n DefaultsCollection,\n Zenmanage,\n type Flag,\n type FlagManager,\n type FlagValue,\n type Logger,\n} from '@zenmanage/sdk';\nimport { buildBootstrapPayload } from './bootstrap';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from './identity';\nimport {\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport { mergeDefaults } from './defaults';\n\nfunction buildClient(options: NextZenmanageClientOptions): Zenmanage {\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(options.environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION)\n .withCacheTtl(options.cacheTtl ?? DEFAULT_SERVER_CACHE_TTL);\n\n if (options.apiEndpoint) {\n builder.withApiEndpoint(options.apiEndpoint);\n }\n\n if (typeof options.enableUsageReporting === 'boolean') {\n builder.withUsageReporting(options.enableUsageReporting);\n }\n\n if (options.logger) {\n builder.withLogger(options.logger);\n }\n\n return new Zenmanage(builder.build());\n}\n\n// A client owns the rules cache, so building one per call would refetch the rules for every\n// flag on every request. Clients are shared per configuration instead, for the life of the\n// server process (or edge isolate). Loggers are compared by identity, hence the WeakMap.\nconst clientsWithoutLogger = new Map<string, Zenmanage>();\nlet clientsByLogger = new WeakMap<Logger, Map<string, Zenmanage>>();\n\nfunction configurationKey(options: NextZenmanageClientOptions): string {\n return JSON.stringify([\n options.environmentToken,\n options.apiEndpoint ?? null,\n options.cacheTtl ?? null,\n options.enableUsageReporting ?? null,\n ]);\n}\n\n/**\n * Returns the client for this configuration, creating it on first use. Calls with the same\n * token, endpoint, TTL, usage setting and logger share one client and so one rules cache.\n */\nexport function createServerClient(options: NextZenmanageClientOptions): Zenmanage {\n let clients = clientsWithoutLogger;\n\n if (options.logger) {\n clients = clientsByLogger.get(options.logger) ?? new Map<string, Zenmanage>();\n clientsByLogger.set(options.logger, clients);\n }\n\n const key = configurationKey(options);\n let client = clients.get(key);\n\n if (!client) {\n client = buildClient(options);\n clients.set(key, client);\n }\n\n return client;\n}\n\n/** Forgets every shared client, and with them their cached rules. Mostly useful in tests. */\nexport function clearServerClients(): void {\n clientsWithoutLogger.clear();\n clientsByLogger = new WeakMap();\n}\n\nexport function createServerRuntime(options: ServerFlagsInput): NextFlagsRuntime {\n const client = createServerClient(options);\n\n // A manager loads its rules once and keeps them, whatever the cache TTL. Handing out the\n // client's own manager would pin the first rules it saw for the life of the process, so\n // every call gets a copy that reads the shared cache (and so honors the TTL) instead.\n let manager = client.flags().withDefaults(options.defaults ?? new DefaultsCollection());\n\n if (options.context) {\n manager = manager.withContext(options.context);\n }\n\n return { client, manager };\n}\n\nexport function createServerManager(options: ServerFlagsInput): FlagManager {\n return createServerRuntime(options).manager;\n}\n\nexport async function getServerFlag(input: ServerFlagInput): Promise<Flag> {\n const manager = createServerManager(input);\n return manager.single(input.key, input.defaultValue);\n}\n\nexport async function getServerFlags(input: ServerFlagsInput): Promise<Flag[]> {\n const manager = createServerManager(input);\n return manager.all();\n}\n\nexport async function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManager> {\n const manager = createServerManager(input);\n await manager.all();\n return manager;\n}\n\n/**\n * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or\n * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the\n * widened type (`string`, `boolean`), so the result can be compared with other values.\n */\nexport async function getFlagValue<T extends FlagValue>(\n input: ServerFlagInput,\n fallback: T\n): Promise<WidenFlagValue<T>> {\n const flag = await getServerFlag(input);\n\n if (typeof fallback === 'boolean') {\n return flag.asBool() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'number') {\n return flag.asNumber() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'string') {\n return flag.asString() as WidenFlagValue<T>;\n }\n\n return flag.asJson() as WidenFlagValue<T>;\n}\n\n/**\n * Evaluates flags on the server and packages them for `NextFlagsProvider`. Pass `keys` to\n * send only the flags client components read.\n */\nexport async function getBootstrapPayload(input: ServerBootstrapInput): Promise<BootstrapPayload> {\n const flags = await getServerFlags(input);\n const wanted = input.keys ? new Set(input.keys) : null;\n\n return buildBootstrapPayload(\n flags.filter((flag) => wanted === null || wanted.has(flag.getKey())),\n { context: input.includeContext ? input.context : undefined }\n );\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.0\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { Attribute, Context, type Flag } from '@zenmanage/sdk';\nimport { getServerFlag } from './server';\nimport type {\n BuildRequestContextOptions,\n MiddlewareFlagInput,\n RequestCookiesLike,\n RequestLike,\n} from './types';\n\nfunction getCookieValue(cookies: RequestCookiesLike | undefined, name: string): string | undefined {\n if (!cookies) {\n return undefined;\n }\n\n const value = cookies.get(name);\n if (typeof value === 'string') {\n return value;\n }\n\n return value?.value;\n}\n\nfunction getRequestPath(request: RequestLike): string {\n if (request.nextUrl?.pathname) {\n return request.nextUrl.pathname;\n }\n\n if (request.url) {\n return new URL(request.url).pathname;\n }\n\n return '/';\n}\n\nfunction decodeHeader(value: string | null): string | undefined {\n if (!value) {\n return undefined;\n }\n\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n// Next.js 15 removed `request.ip` and `request.geo`, so read what the platform forwards.\nfunction getIp(request: RequestLike): string | undefined {\n if (request.ip) {\n return request.ip;\n }\n\n const forwarded = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim();\n return forwarded || request.headers.get('x-real-ip') || undefined;\n}\n\nfunction getGeo(request: RequestLike): { country?: string; region?: string; city?: string } {\n const { headers, geo } = request;\n\n // Next.js 14 and earlier set `request.geo` even when nothing fills it in (it is `{}` when\n // self-hosted), so each field falls back to the headers on its own.\n return {\n country:\n geo?.country ||\n headers.get('x-vercel-ip-country') ||\n headers.get('cf-ipcountry') ||\n undefined,\n region: geo?.region || headers.get('x-vercel-ip-country-region') || undefined,\n city: geo?.city || decodeHeader(headers.get('x-vercel-ip-city')),\n };\n}\n\n/**\n * Builds an evaluation context from a request: an identifier (cookie, then header), the path,\n * the user agent and, when the platform reports it, the visitor's location. Works with\n * `NextRequest` and with a plain `Request`.\n */\nexport function buildContextFromRequest(\n request: RequestLike,\n options: BuildRequestContextOptions = {}\n): Context {\n const identifierCookieName = options.identifierCookieName ?? 'zenmanage_user_id';\n const identifierHeaderName = options.identifierHeaderName ?? 'x-zenmanage-user-id';\n const includeGeo = options.includeGeo ?? true;\n const includeIp = options.includeIp ?? false;\n\n const identifier =\n getCookieValue(request.cookies, identifierCookieName) ??\n request.headers.get(identifierHeaderName) ??\n undefined;\n\n const context = new Context(options.contextType ?? 'user', options.contextName, identifier);\n\n const userAgent = request.headers.get('user-agent');\n if (userAgent) {\n context.addAttribute(new Attribute('user-agent', [userAgent]));\n }\n\n context.addAttribute(new Attribute('path', [getRequestPath(request)]));\n\n const ip = includeIp ? getIp(request) : undefined;\n if (ip) {\n context.addAttribute(new Attribute('ip', [ip]));\n }\n\n if (includeGeo) {\n const geo = getGeo(request);\n\n if (geo.country) {\n context.addAttribute(new Attribute('country', [geo.country]));\n }\n\n if (geo.region) {\n context.addAttribute(new Attribute('region', [geo.region]));\n }\n\n if (geo.city) {\n context.addAttribute(new Attribute('city', [geo.city]));\n }\n }\n\n if (options.additionalAttributes) {\n for (const [key, value] of Object.entries(options.additionalAttributes)) {\n if (!value) {\n continue;\n }\n\n const values = Array.isArray(value) ? value : [value];\n context.addAttribute(new Attribute(key, values));\n }\n }\n\n return context;\n}\n\n/**\n * Evaluates one flag for the request. Runs on the Edge runtime (`middleware.ts`) and on the\n * Node.js runtime (`proxy.ts` in Next.js 16), and needs a server key in both.\n */\nexport async function evaluateMiddlewareFlag(input: MiddlewareFlagInput): Promise<Flag> {\n const context = buildContextFromRequest(input.request, input.requestContext);\n\n return getServerFlag({\n environmentToken: input.environmentToken,\n apiEndpoint: input.apiEndpoint,\n cacheTtl: input.cacheTtl,\n enableUsageReporting: input.enableUsageReporting,\n logger: input.logger,\n key: input.key,\n defaultValue: input.defaultValue,\n defaults: input.defaults,\n context,\n });\n}\n"],"mappings":"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","names":["import_sdk","import_sdk","import_sdk"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/bootstrap.ts","../src/defaults.ts","../src/server.ts","../package.json","../src/identity.ts","../src/middleware.ts"],"sourcesContent":["export {\n DEFAULT_BOOTSTRAP_ELEMENT_ID,\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type BuildBootstrapPayloadOptions,\n type BuildBootstrapScriptOptions,\n type BuildRequestContextOptions,\n type MiddlewareFlagInput,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type RequestCookiesLike,\n type RequestGeoLike,\n type RequestLike,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport {\n buildBootstrapPayload,\n buildBootstrapScript,\n contextFromBootstrapPayload,\n defaultsFromBootstrapDocument,\n defaultsFromBootstrapPayload,\n deserializeBootstrapPayload,\n readBootstrapPayloadFromDocument,\n serializeBootstrapPayload,\n} from './bootstrap';\n\nexport { mergeDefaults } from './defaults';\n\nexport {\n clearServerClients,\n createServerClient,\n createServerManager,\n createServerRuntime,\n getBootstrapPayload,\n getFlagValue,\n getServerFlag,\n getServerFlags,\n preloadServerFlags,\n} from './server';\n\nexport { buildContextFromRequest, evaluateMiddlewareFlag } from './middleware';\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n","import {\n ConfigBuilder,\n DefaultsCollection,\n Zenmanage,\n type Flag,\n type FlagManager,\n type FlagValue,\n type Logger,\n} from '@zenmanage/sdk';\nimport { buildBootstrapPayload } from './bootstrap';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from './identity';\nimport {\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport { mergeDefaults } from './defaults';\n\nfunction buildClient(options: NextZenmanageClientOptions): Zenmanage {\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(options.environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION)\n .withCacheTtl(options.cacheTtl ?? DEFAULT_SERVER_CACHE_TTL);\n\n if (options.apiEndpoint) {\n builder.withApiEndpoint(options.apiEndpoint);\n }\n\n if (typeof options.enableUsageReporting === 'boolean') {\n builder.withUsageReporting(options.enableUsageReporting);\n }\n\n if (options.logger) {\n builder.withLogger(options.logger);\n }\n\n return new Zenmanage(builder.build());\n}\n\n// A client owns the rules cache, so building one per call would refetch the rules for every\n// flag on every request. Clients are shared per configuration instead, for the life of the\n// server process (or edge isolate). Loggers are compared by identity, hence the WeakMap.\nconst clientsWithoutLogger = new Map<string, Zenmanage>();\nlet clientsByLogger = new WeakMap<Logger, Map<string, Zenmanage>>();\n\nfunction configurationKey(options: NextZenmanageClientOptions): string {\n return JSON.stringify([\n options.environmentToken,\n options.apiEndpoint ?? null,\n options.cacheTtl ?? null,\n options.enableUsageReporting ?? null,\n ]);\n}\n\n/**\n * Returns the client for this configuration, creating it on first use. Calls with the same\n * token, endpoint, TTL, usage setting and logger share one client and so one rules cache.\n */\nexport function createServerClient(options: NextZenmanageClientOptions): Zenmanage {\n let clients = clientsWithoutLogger;\n\n if (options.logger) {\n clients = clientsByLogger.get(options.logger) ?? new Map<string, Zenmanage>();\n clientsByLogger.set(options.logger, clients);\n }\n\n const key = configurationKey(options);\n let client = clients.get(key);\n\n if (!client) {\n client = buildClient(options);\n clients.set(key, client);\n }\n\n return client;\n}\n\n/** Forgets every shared client, and with them their cached rules. Mostly useful in tests. */\nexport function clearServerClients(): void {\n clientsWithoutLogger.clear();\n clientsByLogger = new WeakMap();\n}\n\nexport function createServerRuntime(options: ServerFlagsInput): NextFlagsRuntime {\n const client = createServerClient(options);\n\n // A manager loads its rules once and keeps them, whatever the cache TTL. Handing out the\n // client's own manager would pin the first rules it saw for the life of the process, so\n // every call gets a copy that reads the shared cache (and so honors the TTL) instead.\n let manager = client.flags().withDefaults(options.defaults ?? new DefaultsCollection());\n\n if (options.context) {\n manager = manager.withContext(options.context);\n }\n\n return { client, manager };\n}\n\nexport function createServerManager(options: ServerFlagsInput): FlagManager {\n return createServerRuntime(options).manager;\n}\n\nexport async function getServerFlag(input: ServerFlagInput): Promise<Flag> {\n const manager = createServerManager(input);\n return manager.single(input.key, input.defaultValue);\n}\n\nexport async function getServerFlags(input: ServerFlagsInput): Promise<Flag[]> {\n const manager = createServerManager(input);\n return manager.all();\n}\n\nexport async function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManager> {\n const manager = createServerManager(input);\n await manager.all();\n return manager;\n}\n\n/**\n * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or\n * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the\n * widened type (`string`, `boolean`), so the result can be compared with other values.\n *\n * `fallback` is also the default, for a flag that doesn't exist or when the API can't be\n * reached. A `defaultValue` in `input`, or an entry for the key in `input.defaults`, is more\n * specific and wins over it.\n */\nexport async function getFlagValue<T extends FlagValue>(\n input: ServerFlagInput,\n fallback: T\n): Promise<WidenFlagValue<T>> {\n // The SDK prefers an inline default over a defaults collection, so the fallback only goes in\n // when nothing else would supply one. Otherwise it would override the collection's entry.\n const hasOwnDefault = input.defaultValue !== undefined || input.defaults?.has(input.key) === true;\n const flag = await getServerFlag(hasOwnDefault ? input : { ...input, defaultValue: fallback });\n\n if (typeof fallback === 'boolean') {\n return flag.asBool() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'number') {\n return flag.asNumber() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'string') {\n return flag.asString() as WidenFlagValue<T>;\n }\n\n return flag.asJson() as WidenFlagValue<T>;\n}\n\n/**\n * Evaluates flags on the server and packages them for `NextFlagsProvider`. Pass `keys` to\n * send only the flags client components read.\n */\nexport async function getBootstrapPayload(input: ServerBootstrapInput): Promise<BootstrapPayload> {\n const flags = await getServerFlags(input);\n const wanted = input.keys ? new Set(input.keys) : null;\n\n return buildBootstrapPayload(\n flags.filter((flag) => wanted === null || wanted.has(flag.getKey())),\n { context: input.includeContext ? input.context : undefined }\n );\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.1\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { Attribute, Context, type Flag } from '@zenmanage/sdk';\nimport { getServerFlag } from './server';\nimport type {\n BuildRequestContextOptions,\n MiddlewareFlagInput,\n RequestCookiesLike,\n RequestLike,\n} from './types';\n\nfunction getCookieValue(cookies: RequestCookiesLike | undefined, name: string): string | undefined {\n if (!cookies) {\n return undefined;\n }\n\n const value = cookies.get(name);\n if (typeof value === 'string') {\n return value;\n }\n\n return value?.value;\n}\n\nfunction getRequestPath(request: RequestLike): string {\n if (request.nextUrl?.pathname) {\n return request.nextUrl.pathname;\n }\n\n if (request.url) {\n return new URL(request.url).pathname;\n }\n\n return '/';\n}\n\nfunction decodeHeader(value: string | null): string | undefined {\n if (!value) {\n return undefined;\n }\n\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n// Next.js 15 removed `request.ip` and `request.geo`, so read what the platform forwards.\nfunction getIp(request: RequestLike): string | undefined {\n if (request.ip) {\n return request.ip;\n }\n\n const forwarded = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim();\n return forwarded || request.headers.get('x-real-ip') || undefined;\n}\n\nfunction getGeo(request: RequestLike): { country?: string; region?: string; city?: string } {\n const { headers, geo } = request;\n\n // Next.js 14 and earlier set `request.geo` even when nothing fills it in (it is `{}` when\n // self-hosted), so each field falls back to the headers on its own.\n return {\n country:\n geo?.country ||\n headers.get('x-vercel-ip-country') ||\n headers.get('cf-ipcountry') ||\n undefined,\n region: geo?.region || headers.get('x-vercel-ip-country-region') || undefined,\n city: geo?.city || decodeHeader(headers.get('x-vercel-ip-city')),\n };\n}\n\n/**\n * Builds an evaluation context from a request: an identifier (cookie, then header), the path,\n * the user agent and, when the platform reports it, the visitor's location. Works with\n * `NextRequest` and with a plain `Request`.\n */\nexport function buildContextFromRequest(\n request: RequestLike,\n options: BuildRequestContextOptions = {}\n): Context {\n const identifierCookieName = options.identifierCookieName ?? 'zenmanage_user_id';\n const identifierHeaderName = options.identifierHeaderName ?? 'x-zenmanage-user-id';\n const includeGeo = options.includeGeo ?? true;\n const includeIp = options.includeIp ?? false;\n\n const identifier =\n getCookieValue(request.cookies, identifierCookieName) ??\n request.headers.get(identifierHeaderName) ??\n undefined;\n\n const context = new Context(options.contextType ?? 'user', options.contextName, identifier);\n\n const userAgent = request.headers.get('user-agent');\n if (userAgent) {\n context.addAttribute(new Attribute('user-agent', [userAgent]));\n }\n\n context.addAttribute(new Attribute('path', [getRequestPath(request)]));\n\n const ip = includeIp ? getIp(request) : undefined;\n if (ip) {\n context.addAttribute(new Attribute('ip', [ip]));\n }\n\n if (includeGeo) {\n const geo = getGeo(request);\n\n if (geo.country) {\n context.addAttribute(new Attribute('country', [geo.country]));\n }\n\n if (geo.region) {\n context.addAttribute(new Attribute('region', [geo.region]));\n }\n\n if (geo.city) {\n context.addAttribute(new Attribute('city', [geo.city]));\n }\n }\n\n if (options.additionalAttributes) {\n for (const [key, value] of Object.entries(options.additionalAttributes)) {\n if (!value) {\n continue;\n }\n\n const values = Array.isArray(value) ? value : [value];\n context.addAttribute(new Attribute(key, values));\n }\n }\n\n return context;\n}\n\n/**\n * Evaluates one flag for the request. Runs on the Edge runtime (`middleware.ts`) and on the\n * Node.js runtime (`proxy.ts` in Next.js 16), and needs a server key in both.\n */\nexport async function evaluateMiddlewareFlag(input: MiddlewareFlagInput): Promise<Flag> {\n const context = buildContextFromRequest(input.request, input.requestContext);\n\n return getServerFlag({\n environmentToken: input.environmentToken,\n apiEndpoint: input.apiEndpoint,\n cacheTtl: input.cacheTtl,\n enableUsageReporting: input.enableUsageReporting,\n logger: input.logger,\n key: input.key,\n defaultValue: input.defaultValue,\n defaults: input.defaults,\n context,\n });\n}\n"],"mappings":"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","names":["import_sdk","import_sdk","import_sdk"]}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildContextFromRequest,
3
3
  evaluateMiddlewareFlag
4
- } from "./chunk-PMTVMVXJ.js";
4
+ } from "./chunk-HDORXS72.js";
5
5
  import {
6
6
  DEFAULT_BOOTSTRAP_ELEMENT_ID,
7
7
  DEFAULT_SERVER_CACHE_TTL,
@@ -23,7 +23,7 @@ import {
23
23
  preloadServerFlags,
24
24
  readBootstrapPayloadFromDocument,
25
25
  serializeBootstrapPayload
26
- } from "./chunk-ZUB4F2DA.js";
26
+ } from "./chunk-PHODKVFM.js";
27
27
  export {
28
28
  DEFAULT_BOOTSTRAP_ELEMENT_ID,
29
29
  DEFAULT_SERVER_CACHE_TTL,
@@ -33,7 +33,7 @@ var import_sdk = require("@zenmanage/sdk");
33
33
  var DEFAULT_SERVER_CACHE_TTL = 60;
34
34
 
35
35
  // package.json
36
- var version = "1.0.0";
36
+ var version = "1.0.1";
37
37
 
38
38
  // src/identity.ts
39
39
  var CLIENT_AGENT = "zenmanage-nextjs";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/middleware.ts","../src/server.ts","../src/types.ts","../package.json","../src/identity.ts"],"sourcesContent":["import { Attribute, Context, type Flag } from '@zenmanage/sdk';\nimport { getServerFlag } from './server';\nimport type {\n BuildRequestContextOptions,\n MiddlewareFlagInput,\n RequestCookiesLike,\n RequestLike,\n} from './types';\n\nfunction getCookieValue(cookies: RequestCookiesLike | undefined, name: string): string | undefined {\n if (!cookies) {\n return undefined;\n }\n\n const value = cookies.get(name);\n if (typeof value === 'string') {\n return value;\n }\n\n return value?.value;\n}\n\nfunction getRequestPath(request: RequestLike): string {\n if (request.nextUrl?.pathname) {\n return request.nextUrl.pathname;\n }\n\n if (request.url) {\n return new URL(request.url).pathname;\n }\n\n return '/';\n}\n\nfunction decodeHeader(value: string | null): string | undefined {\n if (!value) {\n return undefined;\n }\n\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n// Next.js 15 removed `request.ip` and `request.geo`, so read what the platform forwards.\nfunction getIp(request: RequestLike): string | undefined {\n if (request.ip) {\n return request.ip;\n }\n\n const forwarded = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim();\n return forwarded || request.headers.get('x-real-ip') || undefined;\n}\n\nfunction getGeo(request: RequestLike): { country?: string; region?: string; city?: string } {\n const { headers, geo } = request;\n\n // Next.js 14 and earlier set `request.geo` even when nothing fills it in (it is `{}` when\n // self-hosted), so each field falls back to the headers on its own.\n return {\n country:\n geo?.country ||\n headers.get('x-vercel-ip-country') ||\n headers.get('cf-ipcountry') ||\n undefined,\n region: geo?.region || headers.get('x-vercel-ip-country-region') || undefined,\n city: geo?.city || decodeHeader(headers.get('x-vercel-ip-city')),\n };\n}\n\n/**\n * Builds an evaluation context from a request: an identifier (cookie, then header), the path,\n * the user agent and, when the platform reports it, the visitor's location. Works with\n * `NextRequest` and with a plain `Request`.\n */\nexport function buildContextFromRequest(\n request: RequestLike,\n options: BuildRequestContextOptions = {}\n): Context {\n const identifierCookieName = options.identifierCookieName ?? 'zenmanage_user_id';\n const identifierHeaderName = options.identifierHeaderName ?? 'x-zenmanage-user-id';\n const includeGeo = options.includeGeo ?? true;\n const includeIp = options.includeIp ?? false;\n\n const identifier =\n getCookieValue(request.cookies, identifierCookieName) ??\n request.headers.get(identifierHeaderName) ??\n undefined;\n\n const context = new Context(options.contextType ?? 'user', options.contextName, identifier);\n\n const userAgent = request.headers.get('user-agent');\n if (userAgent) {\n context.addAttribute(new Attribute('user-agent', [userAgent]));\n }\n\n context.addAttribute(new Attribute('path', [getRequestPath(request)]));\n\n const ip = includeIp ? getIp(request) : undefined;\n if (ip) {\n context.addAttribute(new Attribute('ip', [ip]));\n }\n\n if (includeGeo) {\n const geo = getGeo(request);\n\n if (geo.country) {\n context.addAttribute(new Attribute('country', [geo.country]));\n }\n\n if (geo.region) {\n context.addAttribute(new Attribute('region', [geo.region]));\n }\n\n if (geo.city) {\n context.addAttribute(new Attribute('city', [geo.city]));\n }\n }\n\n if (options.additionalAttributes) {\n for (const [key, value] of Object.entries(options.additionalAttributes)) {\n if (!value) {\n continue;\n }\n\n const values = Array.isArray(value) ? value : [value];\n context.addAttribute(new Attribute(key, values));\n }\n }\n\n return context;\n}\n\n/**\n * Evaluates one flag for the request. Runs on the Edge runtime (`middleware.ts`) and on the\n * Node.js runtime (`proxy.ts` in Next.js 16), and needs a server key in both.\n */\nexport async function evaluateMiddlewareFlag(input: MiddlewareFlagInput): Promise<Flag> {\n const context = buildContextFromRequest(input.request, input.requestContext);\n\n return getServerFlag({\n environmentToken: input.environmentToken,\n apiEndpoint: input.apiEndpoint,\n cacheTtl: input.cacheTtl,\n enableUsageReporting: input.enableUsageReporting,\n logger: input.logger,\n key: input.key,\n defaultValue: input.defaultValue,\n defaults: input.defaults,\n context,\n });\n}\n","import {\n ConfigBuilder,\n DefaultsCollection,\n Zenmanage,\n type Flag,\n type FlagManager,\n type FlagValue,\n type Logger,\n} from '@zenmanage/sdk';\nimport { buildBootstrapPayload } from './bootstrap';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from './identity';\nimport {\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport { mergeDefaults } from './defaults';\n\nfunction buildClient(options: NextZenmanageClientOptions): Zenmanage {\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(options.environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION)\n .withCacheTtl(options.cacheTtl ?? DEFAULT_SERVER_CACHE_TTL);\n\n if (options.apiEndpoint) {\n builder.withApiEndpoint(options.apiEndpoint);\n }\n\n if (typeof options.enableUsageReporting === 'boolean') {\n builder.withUsageReporting(options.enableUsageReporting);\n }\n\n if (options.logger) {\n builder.withLogger(options.logger);\n }\n\n return new Zenmanage(builder.build());\n}\n\n// A client owns the rules cache, so building one per call would refetch the rules for every\n// flag on every request. Clients are shared per configuration instead, for the life of the\n// server process (or edge isolate). Loggers are compared by identity, hence the WeakMap.\nconst clientsWithoutLogger = new Map<string, Zenmanage>();\nlet clientsByLogger = new WeakMap<Logger, Map<string, Zenmanage>>();\n\nfunction configurationKey(options: NextZenmanageClientOptions): string {\n return JSON.stringify([\n options.environmentToken,\n options.apiEndpoint ?? null,\n options.cacheTtl ?? null,\n options.enableUsageReporting ?? null,\n ]);\n}\n\n/**\n * Returns the client for this configuration, creating it on first use. Calls with the same\n * token, endpoint, TTL, usage setting and logger share one client and so one rules cache.\n */\nexport function createServerClient(options: NextZenmanageClientOptions): Zenmanage {\n let clients = clientsWithoutLogger;\n\n if (options.logger) {\n clients = clientsByLogger.get(options.logger) ?? new Map<string, Zenmanage>();\n clientsByLogger.set(options.logger, clients);\n }\n\n const key = configurationKey(options);\n let client = clients.get(key);\n\n if (!client) {\n client = buildClient(options);\n clients.set(key, client);\n }\n\n return client;\n}\n\n/** Forgets every shared client, and with them their cached rules. Mostly useful in tests. */\nexport function clearServerClients(): void {\n clientsWithoutLogger.clear();\n clientsByLogger = new WeakMap();\n}\n\nexport function createServerRuntime(options: ServerFlagsInput): NextFlagsRuntime {\n const client = createServerClient(options);\n\n // A manager loads its rules once and keeps them, whatever the cache TTL. Handing out the\n // client's own manager would pin the first rules it saw for the life of the process, so\n // every call gets a copy that reads the shared cache (and so honors the TTL) instead.\n let manager = client.flags().withDefaults(options.defaults ?? new DefaultsCollection());\n\n if (options.context) {\n manager = manager.withContext(options.context);\n }\n\n return { client, manager };\n}\n\nexport function createServerManager(options: ServerFlagsInput): FlagManager {\n return createServerRuntime(options).manager;\n}\n\nexport async function getServerFlag(input: ServerFlagInput): Promise<Flag> {\n const manager = createServerManager(input);\n return manager.single(input.key, input.defaultValue);\n}\n\nexport async function getServerFlags(input: ServerFlagsInput): Promise<Flag[]> {\n const manager = createServerManager(input);\n return manager.all();\n}\n\nexport async function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManager> {\n const manager = createServerManager(input);\n await manager.all();\n return manager;\n}\n\n/**\n * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or\n * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the\n * widened type (`string`, `boolean`), so the result can be compared with other values.\n */\nexport async function getFlagValue<T extends FlagValue>(\n input: ServerFlagInput,\n fallback: T\n): Promise<WidenFlagValue<T>> {\n const flag = await getServerFlag(input);\n\n if (typeof fallback === 'boolean') {\n return flag.asBool() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'number') {\n return flag.asNumber() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'string') {\n return flag.asString() as WidenFlagValue<T>;\n }\n\n return flag.asJson() as WidenFlagValue<T>;\n}\n\n/**\n * Evaluates flags on the server and packages them for `NextFlagsProvider`. Pass `keys` to\n * send only the flags client components read.\n */\nexport async function getBootstrapPayload(input: ServerBootstrapInput): Promise<BootstrapPayload> {\n const flags = await getServerFlags(input);\n const wanted = input.keys ? new Set(input.keys) : null;\n\n return buildBootstrapPayload(\n flags.filter((flag) => wanted === null || wanted.has(flag.getKey())),\n { context: input.includeContext ? input.context : undefined }\n );\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.0\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n"],"mappings":"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","names":["import_sdk"]}
1
+ {"version":3,"sources":["../src/middleware.ts","../src/server.ts","../src/types.ts","../package.json","../src/identity.ts"],"sourcesContent":["import { Attribute, Context, type Flag } from '@zenmanage/sdk';\nimport { getServerFlag } from './server';\nimport type {\n BuildRequestContextOptions,\n MiddlewareFlagInput,\n RequestCookiesLike,\n RequestLike,\n} from './types';\n\nfunction getCookieValue(cookies: RequestCookiesLike | undefined, name: string): string | undefined {\n if (!cookies) {\n return undefined;\n }\n\n const value = cookies.get(name);\n if (typeof value === 'string') {\n return value;\n }\n\n return value?.value;\n}\n\nfunction getRequestPath(request: RequestLike): string {\n if (request.nextUrl?.pathname) {\n return request.nextUrl.pathname;\n }\n\n if (request.url) {\n return new URL(request.url).pathname;\n }\n\n return '/';\n}\n\nfunction decodeHeader(value: string | null): string | undefined {\n if (!value) {\n return undefined;\n }\n\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n// Next.js 15 removed `request.ip` and `request.geo`, so read what the platform forwards.\nfunction getIp(request: RequestLike): string | undefined {\n if (request.ip) {\n return request.ip;\n }\n\n const forwarded = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim();\n return forwarded || request.headers.get('x-real-ip') || undefined;\n}\n\nfunction getGeo(request: RequestLike): { country?: string; region?: string; city?: string } {\n const { headers, geo } = request;\n\n // Next.js 14 and earlier set `request.geo` even when nothing fills it in (it is `{}` when\n // self-hosted), so each field falls back to the headers on its own.\n return {\n country:\n geo?.country ||\n headers.get('x-vercel-ip-country') ||\n headers.get('cf-ipcountry') ||\n undefined,\n region: geo?.region || headers.get('x-vercel-ip-country-region') || undefined,\n city: geo?.city || decodeHeader(headers.get('x-vercel-ip-city')),\n };\n}\n\n/**\n * Builds an evaluation context from a request: an identifier (cookie, then header), the path,\n * the user agent and, when the platform reports it, the visitor's location. Works with\n * `NextRequest` and with a plain `Request`.\n */\nexport function buildContextFromRequest(\n request: RequestLike,\n options: BuildRequestContextOptions = {}\n): Context {\n const identifierCookieName = options.identifierCookieName ?? 'zenmanage_user_id';\n const identifierHeaderName = options.identifierHeaderName ?? 'x-zenmanage-user-id';\n const includeGeo = options.includeGeo ?? true;\n const includeIp = options.includeIp ?? false;\n\n const identifier =\n getCookieValue(request.cookies, identifierCookieName) ??\n request.headers.get(identifierHeaderName) ??\n undefined;\n\n const context = new Context(options.contextType ?? 'user', options.contextName, identifier);\n\n const userAgent = request.headers.get('user-agent');\n if (userAgent) {\n context.addAttribute(new Attribute('user-agent', [userAgent]));\n }\n\n context.addAttribute(new Attribute('path', [getRequestPath(request)]));\n\n const ip = includeIp ? getIp(request) : undefined;\n if (ip) {\n context.addAttribute(new Attribute('ip', [ip]));\n }\n\n if (includeGeo) {\n const geo = getGeo(request);\n\n if (geo.country) {\n context.addAttribute(new Attribute('country', [geo.country]));\n }\n\n if (geo.region) {\n context.addAttribute(new Attribute('region', [geo.region]));\n }\n\n if (geo.city) {\n context.addAttribute(new Attribute('city', [geo.city]));\n }\n }\n\n if (options.additionalAttributes) {\n for (const [key, value] of Object.entries(options.additionalAttributes)) {\n if (!value) {\n continue;\n }\n\n const values = Array.isArray(value) ? value : [value];\n context.addAttribute(new Attribute(key, values));\n }\n }\n\n return context;\n}\n\n/**\n * Evaluates one flag for the request. Runs on the Edge runtime (`middleware.ts`) and on the\n * Node.js runtime (`proxy.ts` in Next.js 16), and needs a server key in both.\n */\nexport async function evaluateMiddlewareFlag(input: MiddlewareFlagInput): Promise<Flag> {\n const context = buildContextFromRequest(input.request, input.requestContext);\n\n return getServerFlag({\n environmentToken: input.environmentToken,\n apiEndpoint: input.apiEndpoint,\n cacheTtl: input.cacheTtl,\n enableUsageReporting: input.enableUsageReporting,\n logger: input.logger,\n key: input.key,\n defaultValue: input.defaultValue,\n defaults: input.defaults,\n context,\n });\n}\n","import {\n ConfigBuilder,\n DefaultsCollection,\n Zenmanage,\n type Flag,\n type FlagManager,\n type FlagValue,\n type Logger,\n} from '@zenmanage/sdk';\nimport { buildBootstrapPayload } from './bootstrap';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from './identity';\nimport {\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport { mergeDefaults } from './defaults';\n\nfunction buildClient(options: NextZenmanageClientOptions): Zenmanage {\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(options.environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION)\n .withCacheTtl(options.cacheTtl ?? DEFAULT_SERVER_CACHE_TTL);\n\n if (options.apiEndpoint) {\n builder.withApiEndpoint(options.apiEndpoint);\n }\n\n if (typeof options.enableUsageReporting === 'boolean') {\n builder.withUsageReporting(options.enableUsageReporting);\n }\n\n if (options.logger) {\n builder.withLogger(options.logger);\n }\n\n return new Zenmanage(builder.build());\n}\n\n// A client owns the rules cache, so building one per call would refetch the rules for every\n// flag on every request. Clients are shared per configuration instead, for the life of the\n// server process (or edge isolate). Loggers are compared by identity, hence the WeakMap.\nconst clientsWithoutLogger = new Map<string, Zenmanage>();\nlet clientsByLogger = new WeakMap<Logger, Map<string, Zenmanage>>();\n\nfunction configurationKey(options: NextZenmanageClientOptions): string {\n return JSON.stringify([\n options.environmentToken,\n options.apiEndpoint ?? null,\n options.cacheTtl ?? null,\n options.enableUsageReporting ?? null,\n ]);\n}\n\n/**\n * Returns the client for this configuration, creating it on first use. Calls with the same\n * token, endpoint, TTL, usage setting and logger share one client and so one rules cache.\n */\nexport function createServerClient(options: NextZenmanageClientOptions): Zenmanage {\n let clients = clientsWithoutLogger;\n\n if (options.logger) {\n clients = clientsByLogger.get(options.logger) ?? new Map<string, Zenmanage>();\n clientsByLogger.set(options.logger, clients);\n }\n\n const key = configurationKey(options);\n let client = clients.get(key);\n\n if (!client) {\n client = buildClient(options);\n clients.set(key, client);\n }\n\n return client;\n}\n\n/** Forgets every shared client, and with them their cached rules. Mostly useful in tests. */\nexport function clearServerClients(): void {\n clientsWithoutLogger.clear();\n clientsByLogger = new WeakMap();\n}\n\nexport function createServerRuntime(options: ServerFlagsInput): NextFlagsRuntime {\n const client = createServerClient(options);\n\n // A manager loads its rules once and keeps them, whatever the cache TTL. Handing out the\n // client's own manager would pin the first rules it saw for the life of the process, so\n // every call gets a copy that reads the shared cache (and so honors the TTL) instead.\n let manager = client.flags().withDefaults(options.defaults ?? new DefaultsCollection());\n\n if (options.context) {\n manager = manager.withContext(options.context);\n }\n\n return { client, manager };\n}\n\nexport function createServerManager(options: ServerFlagsInput): FlagManager {\n return createServerRuntime(options).manager;\n}\n\nexport async function getServerFlag(input: ServerFlagInput): Promise<Flag> {\n const manager = createServerManager(input);\n return manager.single(input.key, input.defaultValue);\n}\n\nexport async function getServerFlags(input: ServerFlagsInput): Promise<Flag[]> {\n const manager = createServerManager(input);\n return manager.all();\n}\n\nexport async function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManager> {\n const manager = createServerManager(input);\n await manager.all();\n return manager;\n}\n\n/**\n * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or\n * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the\n * widened type (`string`, `boolean`), so the result can be compared with other values.\n *\n * `fallback` is also the default, for a flag that doesn't exist or when the API can't be\n * reached. A `defaultValue` in `input`, or an entry for the key in `input.defaults`, is more\n * specific and wins over it.\n */\nexport async function getFlagValue<T extends FlagValue>(\n input: ServerFlagInput,\n fallback: T\n): Promise<WidenFlagValue<T>> {\n // The SDK prefers an inline default over a defaults collection, so the fallback only goes in\n // when nothing else would supply one. Otherwise it would override the collection's entry.\n const hasOwnDefault = input.defaultValue !== undefined || input.defaults?.has(input.key) === true;\n const flag = await getServerFlag(hasOwnDefault ? input : { ...input, defaultValue: fallback });\n\n if (typeof fallback === 'boolean') {\n return flag.asBool() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'number') {\n return flag.asNumber() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'string') {\n return flag.asString() as WidenFlagValue<T>;\n }\n\n return flag.asJson() as WidenFlagValue<T>;\n}\n\n/**\n * Evaluates flags on the server and packages them for `NextFlagsProvider`. Pass `keys` to\n * send only the flags client components read.\n */\nexport async function getBootstrapPayload(input: ServerBootstrapInput): Promise<BootstrapPayload> {\n const flags = await getServerFlags(input);\n const wanted = input.keys ? new Set(input.keys) : null;\n\n return buildBootstrapPayload(\n flags.filter((flag) => wanted === null || wanted.has(flag.getKey())),\n { context: input.includeContext ? input.context : undefined }\n );\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.1\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n"],"mappings":"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","names":["import_sdk"]}
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  buildContextFromRequest,
3
3
  evaluateMiddlewareFlag
4
- } from "./chunk-PMTVMVXJ.js";
5
- import "./chunk-ZUB4F2DA.js";
4
+ } from "./chunk-HDORXS72.js";
5
+ import "./chunk-PHODKVFM.js";
6
6
  export {
7
7
  buildContextFromRequest,
8
8
  evaluateMiddlewareFlag
package/dist/server.cjs CHANGED
@@ -52,7 +52,7 @@ function buildBootstrapPayload(flags, options = {}) {
52
52
  }
53
53
 
54
54
  // package.json
55
- var version = "1.0.0";
55
+ var version = "1.0.1";
56
56
 
57
57
  // src/identity.ts
58
58
  var CLIENT_AGENT = "zenmanage-nextjs";
@@ -146,7 +146,8 @@ async function preloadServerFlags(input) {
146
146
  return manager;
147
147
  }
148
148
  async function getFlagValue(input, fallback) {
149
- const flag = await getServerFlag(input);
149
+ const hasOwnDefault = input.defaultValue !== void 0 || input.defaults?.has(input.key) === true;
150
+ const flag = await getServerFlag(hasOwnDefault ? input : { ...input, defaultValue: fallback });
150
151
  if (typeof fallback === "boolean") {
151
152
  return flag.asBool();
152
153
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/bootstrap.ts","../src/types.ts","../package.json","../src/identity.ts","../src/defaults.ts"],"sourcesContent":["import {\n ConfigBuilder,\n DefaultsCollection,\n Zenmanage,\n type Flag,\n type FlagManager,\n type FlagValue,\n type Logger,\n} from '@zenmanage/sdk';\nimport { buildBootstrapPayload } from './bootstrap';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from './identity';\nimport {\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport { mergeDefaults } from './defaults';\n\nfunction buildClient(options: NextZenmanageClientOptions): Zenmanage {\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(options.environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION)\n .withCacheTtl(options.cacheTtl ?? DEFAULT_SERVER_CACHE_TTL);\n\n if (options.apiEndpoint) {\n builder.withApiEndpoint(options.apiEndpoint);\n }\n\n if (typeof options.enableUsageReporting === 'boolean') {\n builder.withUsageReporting(options.enableUsageReporting);\n }\n\n if (options.logger) {\n builder.withLogger(options.logger);\n }\n\n return new Zenmanage(builder.build());\n}\n\n// A client owns the rules cache, so building one per call would refetch the rules for every\n// flag on every request. Clients are shared per configuration instead, for the life of the\n// server process (or edge isolate). Loggers are compared by identity, hence the WeakMap.\nconst clientsWithoutLogger = new Map<string, Zenmanage>();\nlet clientsByLogger = new WeakMap<Logger, Map<string, Zenmanage>>();\n\nfunction configurationKey(options: NextZenmanageClientOptions): string {\n return JSON.stringify([\n options.environmentToken,\n options.apiEndpoint ?? null,\n options.cacheTtl ?? null,\n options.enableUsageReporting ?? null,\n ]);\n}\n\n/**\n * Returns the client for this configuration, creating it on first use. Calls with the same\n * token, endpoint, TTL, usage setting and logger share one client and so one rules cache.\n */\nexport function createServerClient(options: NextZenmanageClientOptions): Zenmanage {\n let clients = clientsWithoutLogger;\n\n if (options.logger) {\n clients = clientsByLogger.get(options.logger) ?? new Map<string, Zenmanage>();\n clientsByLogger.set(options.logger, clients);\n }\n\n const key = configurationKey(options);\n let client = clients.get(key);\n\n if (!client) {\n client = buildClient(options);\n clients.set(key, client);\n }\n\n return client;\n}\n\n/** Forgets every shared client, and with them their cached rules. Mostly useful in tests. */\nexport function clearServerClients(): void {\n clientsWithoutLogger.clear();\n clientsByLogger = new WeakMap();\n}\n\nexport function createServerRuntime(options: ServerFlagsInput): NextFlagsRuntime {\n const client = createServerClient(options);\n\n // A manager loads its rules once and keeps them, whatever the cache TTL. Handing out the\n // client's own manager would pin the first rules it saw for the life of the process, so\n // every call gets a copy that reads the shared cache (and so honors the TTL) instead.\n let manager = client.flags().withDefaults(options.defaults ?? new DefaultsCollection());\n\n if (options.context) {\n manager = manager.withContext(options.context);\n }\n\n return { client, manager };\n}\n\nexport function createServerManager(options: ServerFlagsInput): FlagManager {\n return createServerRuntime(options).manager;\n}\n\nexport async function getServerFlag(input: ServerFlagInput): Promise<Flag> {\n const manager = createServerManager(input);\n return manager.single(input.key, input.defaultValue);\n}\n\nexport async function getServerFlags(input: ServerFlagsInput): Promise<Flag[]> {\n const manager = createServerManager(input);\n return manager.all();\n}\n\nexport async function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManager> {\n const manager = createServerManager(input);\n await manager.all();\n return manager;\n}\n\n/**\n * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or\n * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the\n * widened type (`string`, `boolean`), so the result can be compared with other values.\n */\nexport async function getFlagValue<T extends FlagValue>(\n input: ServerFlagInput,\n fallback: T\n): Promise<WidenFlagValue<T>> {\n const flag = await getServerFlag(input);\n\n if (typeof fallback === 'boolean') {\n return flag.asBool() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'number') {\n return flag.asNumber() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'string') {\n return flag.asString() as WidenFlagValue<T>;\n }\n\n return flag.asJson() as WidenFlagValue<T>;\n}\n\n/**\n * Evaluates flags on the server and packages them for `NextFlagsProvider`. Pass `keys` to\n * send only the flags client components read.\n */\nexport async function getBootstrapPayload(input: ServerBootstrapInput): Promise<BootstrapPayload> {\n const flags = await getServerFlags(input);\n const wanted = input.keys ? new Set(input.keys) : null;\n\n return buildBootstrapPayload(\n flags.filter((flag) => wanted === null || wanted.has(flag.getKey())),\n { context: input.includeContext ? input.context : undefined }\n );\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.0\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n"],"mappings":"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","names":["import_sdk","import_sdk"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/bootstrap.ts","../src/types.ts","../package.json","../src/identity.ts","../src/defaults.ts"],"sourcesContent":["import {\n ConfigBuilder,\n DefaultsCollection,\n Zenmanage,\n type Flag,\n type FlagManager,\n type FlagValue,\n type Logger,\n} from '@zenmanage/sdk';\nimport { buildBootstrapPayload } from './bootstrap';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from './identity';\nimport {\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport { mergeDefaults } from './defaults';\n\nfunction buildClient(options: NextZenmanageClientOptions): Zenmanage {\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(options.environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION)\n .withCacheTtl(options.cacheTtl ?? DEFAULT_SERVER_CACHE_TTL);\n\n if (options.apiEndpoint) {\n builder.withApiEndpoint(options.apiEndpoint);\n }\n\n if (typeof options.enableUsageReporting === 'boolean') {\n builder.withUsageReporting(options.enableUsageReporting);\n }\n\n if (options.logger) {\n builder.withLogger(options.logger);\n }\n\n return new Zenmanage(builder.build());\n}\n\n// A client owns the rules cache, so building one per call would refetch the rules for every\n// flag on every request. Clients are shared per configuration instead, for the life of the\n// server process (or edge isolate). Loggers are compared by identity, hence the WeakMap.\nconst clientsWithoutLogger = new Map<string, Zenmanage>();\nlet clientsByLogger = new WeakMap<Logger, Map<string, Zenmanage>>();\n\nfunction configurationKey(options: NextZenmanageClientOptions): string {\n return JSON.stringify([\n options.environmentToken,\n options.apiEndpoint ?? null,\n options.cacheTtl ?? null,\n options.enableUsageReporting ?? null,\n ]);\n}\n\n/**\n * Returns the client for this configuration, creating it on first use. Calls with the same\n * token, endpoint, TTL, usage setting and logger share one client and so one rules cache.\n */\nexport function createServerClient(options: NextZenmanageClientOptions): Zenmanage {\n let clients = clientsWithoutLogger;\n\n if (options.logger) {\n clients = clientsByLogger.get(options.logger) ?? new Map<string, Zenmanage>();\n clientsByLogger.set(options.logger, clients);\n }\n\n const key = configurationKey(options);\n let client = clients.get(key);\n\n if (!client) {\n client = buildClient(options);\n clients.set(key, client);\n }\n\n return client;\n}\n\n/** Forgets every shared client, and with them their cached rules. Mostly useful in tests. */\nexport function clearServerClients(): void {\n clientsWithoutLogger.clear();\n clientsByLogger = new WeakMap();\n}\n\nexport function createServerRuntime(options: ServerFlagsInput): NextFlagsRuntime {\n const client = createServerClient(options);\n\n // A manager loads its rules once and keeps them, whatever the cache TTL. Handing out the\n // client's own manager would pin the first rules it saw for the life of the process, so\n // every call gets a copy that reads the shared cache (and so honors the TTL) instead.\n let manager = client.flags().withDefaults(options.defaults ?? new DefaultsCollection());\n\n if (options.context) {\n manager = manager.withContext(options.context);\n }\n\n return { client, manager };\n}\n\nexport function createServerManager(options: ServerFlagsInput): FlagManager {\n return createServerRuntime(options).manager;\n}\n\nexport async function getServerFlag(input: ServerFlagInput): Promise<Flag> {\n const manager = createServerManager(input);\n return manager.single(input.key, input.defaultValue);\n}\n\nexport async function getServerFlags(input: ServerFlagsInput): Promise<Flag[]> {\n const manager = createServerManager(input);\n return manager.all();\n}\n\nexport async function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManager> {\n const manager = createServerManager(input);\n await manager.all();\n return manager;\n}\n\n/**\n * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or\n * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the\n * widened type (`string`, `boolean`), so the result can be compared with other values.\n *\n * `fallback` is also the default, for a flag that doesn't exist or when the API can't be\n * reached. A `defaultValue` in `input`, or an entry for the key in `input.defaults`, is more\n * specific and wins over it.\n */\nexport async function getFlagValue<T extends FlagValue>(\n input: ServerFlagInput,\n fallback: T\n): Promise<WidenFlagValue<T>> {\n // The SDK prefers an inline default over a defaults collection, so the fallback only goes in\n // when nothing else would supply one. Otherwise it would override the collection's entry.\n const hasOwnDefault = input.defaultValue !== undefined || input.defaults?.has(input.key) === true;\n const flag = await getServerFlag(hasOwnDefault ? input : { ...input, defaultValue: fallback });\n\n if (typeof fallback === 'boolean') {\n return flag.asBool() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'number') {\n return flag.asNumber() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'string') {\n return flag.asString() as WidenFlagValue<T>;\n }\n\n return flag.asJson() as WidenFlagValue<T>;\n}\n\n/**\n * Evaluates flags on the server and packages them for `NextFlagsProvider`. Pass `keys` to\n * send only the flags client components read.\n */\nexport async function getBootstrapPayload(input: ServerBootstrapInput): Promise<BootstrapPayload> {\n const flags = await getServerFlags(input);\n const wanted = input.keys ? new Set(input.keys) : null;\n\n return buildBootstrapPayload(\n flags.filter((flag) => wanted === null || wanted.has(flag.getKey())),\n { context: input.includeContext ? input.context : undefined }\n );\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.1\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n"],"mappings":"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","names":["import_sdk","import_sdk"]}
package/dist/server.d.cts CHANGED
@@ -20,6 +20,10 @@ declare function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManage
20
20
  * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or
21
21
  * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the
22
22
  * widened type (`string`, `boolean`), so the result can be compared with other values.
23
+ *
24
+ * `fallback` is also the default, for a flag that doesn't exist or when the API can't be
25
+ * reached. A `defaultValue` in `input`, or an entry for the key in `input.defaults`, is more
26
+ * specific and wins over it.
23
27
  */
24
28
  declare function getFlagValue<T extends FlagValue>(input: ServerFlagInput, fallback: T): Promise<WidenFlagValue<T>>;
25
29
  /**
package/dist/server.d.ts CHANGED
@@ -20,6 +20,10 @@ declare function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManage
20
20
  * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or
21
21
  * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the
22
22
  * widened type (`string`, `boolean`), so the result can be compared with other values.
23
+ *
24
+ * `fallback` is also the default, for a flag that doesn't exist or when the API can't be
25
+ * reached. A `defaultValue` in `input`, or an entry for the key in `input.defaults`, is more
26
+ * specific and wins over it.
23
27
  */
24
28
  declare function getFlagValue<T extends FlagValue>(input: ServerFlagInput, fallback: T): Promise<WidenFlagValue<T>>;
25
29
  /**
package/dist/server.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  getServerFlags,
10
10
  mergeDefaults,
11
11
  preloadServerFlags
12
- } from "./chunk-ZUB4F2DA.js";
12
+ } from "./chunk-PHODKVFM.js";
13
13
  export {
14
14
  clearServerClients,
15
15
  createServerClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenmanage/nextjs",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server.ts","../src/bootstrap.ts","../src/types.ts","../package.json","../src/identity.ts","../src/defaults.ts"],"sourcesContent":["import {\n ConfigBuilder,\n DefaultsCollection,\n Zenmanage,\n type Flag,\n type FlagManager,\n type FlagValue,\n type Logger,\n} from '@zenmanage/sdk';\nimport { buildBootstrapPayload } from './bootstrap';\nimport { CLIENT_AGENT, PACKAGE_VERSION } from './identity';\nimport {\n DEFAULT_SERVER_CACHE_TTL,\n type BootstrapPayload,\n type NextFlagsRuntime,\n type NextZenmanageClientOptions,\n type ServerBootstrapInput,\n type ServerFlagInput,\n type ServerFlagsInput,\n type WidenFlagValue,\n} from './types';\n\nexport { mergeDefaults } from './defaults';\n\nfunction buildClient(options: NextZenmanageClientOptions): Zenmanage {\n const builder = ConfigBuilder.create()\n .withEnvironmentToken(options.environmentToken)\n .withClientAgent(CLIENT_AGENT)\n .withSdkVersion(PACKAGE_VERSION)\n .withCacheTtl(options.cacheTtl ?? DEFAULT_SERVER_CACHE_TTL);\n\n if (options.apiEndpoint) {\n builder.withApiEndpoint(options.apiEndpoint);\n }\n\n if (typeof options.enableUsageReporting === 'boolean') {\n builder.withUsageReporting(options.enableUsageReporting);\n }\n\n if (options.logger) {\n builder.withLogger(options.logger);\n }\n\n return new Zenmanage(builder.build());\n}\n\n// A client owns the rules cache, so building one per call would refetch the rules for every\n// flag on every request. Clients are shared per configuration instead, for the life of the\n// server process (or edge isolate). Loggers are compared by identity, hence the WeakMap.\nconst clientsWithoutLogger = new Map<string, Zenmanage>();\nlet clientsByLogger = new WeakMap<Logger, Map<string, Zenmanage>>();\n\nfunction configurationKey(options: NextZenmanageClientOptions): string {\n return JSON.stringify([\n options.environmentToken,\n options.apiEndpoint ?? null,\n options.cacheTtl ?? null,\n options.enableUsageReporting ?? null,\n ]);\n}\n\n/**\n * Returns the client for this configuration, creating it on first use. Calls with the same\n * token, endpoint, TTL, usage setting and logger share one client and so one rules cache.\n */\nexport function createServerClient(options: NextZenmanageClientOptions): Zenmanage {\n let clients = clientsWithoutLogger;\n\n if (options.logger) {\n clients = clientsByLogger.get(options.logger) ?? new Map<string, Zenmanage>();\n clientsByLogger.set(options.logger, clients);\n }\n\n const key = configurationKey(options);\n let client = clients.get(key);\n\n if (!client) {\n client = buildClient(options);\n clients.set(key, client);\n }\n\n return client;\n}\n\n/** Forgets every shared client, and with them their cached rules. Mostly useful in tests. */\nexport function clearServerClients(): void {\n clientsWithoutLogger.clear();\n clientsByLogger = new WeakMap();\n}\n\nexport function createServerRuntime(options: ServerFlagsInput): NextFlagsRuntime {\n const client = createServerClient(options);\n\n // A manager loads its rules once and keeps them, whatever the cache TTL. Handing out the\n // client's own manager would pin the first rules it saw for the life of the process, so\n // every call gets a copy that reads the shared cache (and so honors the TTL) instead.\n let manager = client.flags().withDefaults(options.defaults ?? new DefaultsCollection());\n\n if (options.context) {\n manager = manager.withContext(options.context);\n }\n\n return { client, manager };\n}\n\nexport function createServerManager(options: ServerFlagsInput): FlagManager {\n return createServerRuntime(options).manager;\n}\n\nexport async function getServerFlag(input: ServerFlagInput): Promise<Flag> {\n const manager = createServerManager(input);\n return manager.single(input.key, input.defaultValue);\n}\n\nexport async function getServerFlags(input: ServerFlagsInput): Promise<Flag[]> {\n const manager = createServerManager(input);\n return manager.all();\n}\n\nexport async function preloadServerFlags(input: ServerFlagsInput): Promise<FlagManager> {\n const manager = createServerManager(input);\n await manager.all();\n return manager;\n}\n\n/**\n * Evaluates a flag and returns its value as the type of `fallback`: a boolean, number or\n * string, or a JSON object or array. A literal fallback (`'control'`, `false`) gives the\n * widened type (`string`, `boolean`), so the result can be compared with other values.\n */\nexport async function getFlagValue<T extends FlagValue>(\n input: ServerFlagInput,\n fallback: T\n): Promise<WidenFlagValue<T>> {\n const flag = await getServerFlag(input);\n\n if (typeof fallback === 'boolean') {\n return flag.asBool() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'number') {\n return flag.asNumber() as WidenFlagValue<T>;\n }\n\n if (typeof fallback === 'string') {\n return flag.asString() as WidenFlagValue<T>;\n }\n\n return flag.asJson() as WidenFlagValue<T>;\n}\n\n/**\n * Evaluates flags on the server and packages them for `NextFlagsProvider`. Pass `keys` to\n * send only the flags client components read.\n */\nexport async function getBootstrapPayload(input: ServerBootstrapInput): Promise<BootstrapPayload> {\n const flags = await getServerFlags(input);\n const wanted = input.keys ? new Set(input.keys) : null;\n\n return buildBootstrapPayload(\n flags.filter((flag) => wanted === null || wanted.has(flag.getKey())),\n { context: input.includeContext ? input.context : undefined }\n );\n}\n","import {\n Context,\n DefaultsCollection,\n type ContextData,\n type Flag,\n type FlagValue,\n} from '@zenmanage/sdk';\nimport type {\n BootstrapPayload,\n BuildBootstrapPayloadOptions,\n BuildBootstrapScriptOptions,\n} from './types';\nimport { DEFAULT_BOOTSTRAP_ELEMENT_ID } from './types';\n\nconst ELEMENT_ID_PATTERN = /^[A-Za-z_][\\w:.-]*$/;\n\nfunction escapeForScriptTag(value: string): string {\n return value\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFlagValue(value: unknown): value is FlagValue {\n return (\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\n/**\n * Builds the payload `NextFlagsProvider` reads. Accepts the flags straight from\n * `getServerFlags`, or a plain `{ key: value }` record.\n */\nexport function buildBootstrapPayload(\n flags: Flag[] | Record<string, FlagValue>,\n options: BuildBootstrapPayloadOptions = {}\n): BootstrapPayload {\n const values = Array.isArray(flags)\n ? Object.fromEntries(flags.map((flag) => [flag.getKey(), flag.getValue()]))\n : flags;\n\n // `context` is left out rather than set to undefined: Next.js refuses undefined in the props\n // `getServerSideProps` returns, and this payload is meant to be passed there.\n return {\n version: 1,\n generatedAt: (options.generatedAt ?? new Date()).toISOString(),\n flags: values,\n ...(options.context ? { context: options.context.toJSON() } : {}),\n };\n}\n\nexport function serializeBootstrapPayload(payload: BootstrapPayload): string {\n return escapeForScriptTag(JSON.stringify(payload));\n}\n\nexport function deserializeBootstrapPayload(serializedPayload: string): BootstrapPayload | null {\n try {\n const parsed: unknown = JSON.parse(serializedPayload);\n\n if (\n !isPlainObject(parsed) ||\n parsed.version !== 1 ||\n !isPlainObject(parsed.flags) ||\n typeof parsed.generatedAt !== 'string'\n ) {\n return null;\n }\n\n const flags = Object.fromEntries(\n Object.entries(parsed.flags).filter(([, value]) => isFlagValue(value))\n ) as Record<string, FlagValue>;\n\n return {\n version: 1,\n generatedAt: parsed.generatedAt,\n flags,\n ...(isPlainObject(parsed.context)\n ? { context: parsed.context as unknown as ContextData }\n : {}),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Renders the payload as a JSON `<script>` tag for pages that can't pass props to the provider.\n * Prefer the `bootstrapPayload` prop: only a prop is identical on the server and in the browser.\n */\nexport function buildBootstrapScript(\n payload: BootstrapPayload,\n options: BuildBootstrapScriptOptions = {}\n): string {\n const elementId = options.elementId ?? DEFAULT_BOOTSTRAP_ELEMENT_ID;\n\n if (!ELEMENT_ID_PATTERN.test(elementId)) {\n throw new Error(`Invalid bootstrap element id \"${elementId}\".`);\n }\n\n const serialized = serializeBootstrapPayload(payload);\n return `<script id=\"${elementId}\" type=\"application/json\">${serialized}</script>`;\n}\n\nexport function readBootstrapPayloadFromDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): BootstrapPayload | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const element = document.getElementById(elementId);\n if (!element) {\n return null;\n }\n\n const text = element.textContent ?? '';\n return deserializeBootstrapPayload(text);\n}\n\nexport function defaultsFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): DefaultsCollection | undefined {\n if (!payload) {\n return undefined;\n }\n\n return DefaultsCollection.fromObject(payload.flags);\n}\n\nexport function defaultsFromBootstrapDocument(\n elementId = DEFAULT_BOOTSTRAP_ELEMENT_ID\n): DefaultsCollection | undefined {\n return defaultsFromBootstrapPayload(readBootstrapPayloadFromDocument(elementId));\n}\n\nexport function contextFromBootstrapPayload(\n payload: BootstrapPayload | null | undefined\n): Context | undefined {\n if (!payload?.context) {\n return undefined;\n }\n\n try {\n return Context.fromObject(payload.context);\n } catch {\n return undefined;\n }\n}\n","import type {\n Context,\n ContextData,\n DefaultsCollection,\n FlagManager,\n FlagValue,\n Logger,\n Zenmanage,\n} from '@zenmanage/sdk';\nimport type { FlagsProviderProps } from '@zenmanage/react';\n\nexport const DEFAULT_BOOTSTRAP_ELEMENT_ID = '__ZENMANAGE_BOOTSTRAP__';\n\n/**\n * Rules are cached in memory for this many seconds. The SDK's own default is an hour, which\n * would leave a long-running `next start` server on stale rules; a minute keeps flag changes\n * visible quickly while still sharing one fetch across every request and component.\n */\nexport const DEFAULT_SERVER_CACHE_TTL = 60;\n\n/**\n * Widens a literal (`false`, `'control'`, `1500`) to its primitive type, so a value read with\n * a literal fallback is `boolean`, `string` or `number` rather than the literal. Object and\n * array values (JSON flags) pass through unchanged.\n */\nexport type WidenFlagValue<T extends FlagValue> = T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T;\n\nexport interface NextZenmanageClientOptions {\n /** A server key (`srv_...`). Server and edge runtimes reject client keys. */\n environmentToken: string;\n apiEndpoint?: string;\n /** Seconds to cache rules in memory. Defaults to {@link DEFAULT_SERVER_CACHE_TTL}. */\n cacheTtl?: number;\n enableUsageReporting?: boolean;\n logger?: Logger;\n}\n\nexport interface ServerFlagInput extends NextZenmanageClientOptions {\n key: string;\n defaultValue?: FlagValue;\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerFlagsInput extends NextZenmanageClientOptions {\n context?: Context;\n defaults?: DefaultsCollection;\n}\n\nexport interface ServerBootstrapInput extends ServerFlagsInput {\n /**\n * The flags to send to the browser. Bootstrapping every flag puts each flag's key and\n * value in the page source, so list only what client components read.\n */\n keys?: string[];\n /** Include the evaluation context in the payload so the browser evaluates for the same user. */\n includeContext?: boolean;\n}\n\nexport interface BootstrapPayload {\n version: 1;\n generatedAt: string;\n flags: Record<string, FlagValue>;\n context?: ContextData;\n}\n\nexport interface BuildBootstrapPayloadOptions {\n context?: Context;\n generatedAt?: Date;\n}\n\nexport interface BuildBootstrapScriptOptions {\n elementId?: string;\n}\n\nexport interface RequestCookieValue {\n value: string;\n}\n\nexport interface RequestCookiesLike {\n get(name: string): RequestCookieValue | string | undefined;\n}\n\nexport interface RequestGeoLike {\n country?: string;\n region?: string;\n city?: string;\n}\n\nexport interface RequestLike {\n headers: Headers;\n cookies?: RequestCookiesLike;\n nextUrl?: { pathname: string };\n url?: string;\n /** Removed from `NextRequest` in Next.js 15; the forwarding headers are used instead. */\n ip?: string | null;\n /** Removed from `NextRequest` in Next.js 15; the Vercel and Cloudflare headers are used instead. */\n geo?: RequestGeoLike;\n}\n\nexport interface BuildRequestContextOptions {\n contextType?: string;\n contextName?: string;\n identifierCookieName?: string;\n identifierHeaderName?: string;\n /** Add `country`, `region` and `city` attributes when the platform reports them. Default `true`. */\n includeGeo?: boolean;\n /** Add the visitor's IP address as an `ip` attribute. Default `false`: an IP is personal data. */\n includeIp?: boolean;\n additionalAttributes?: Record<string, string | string[] | undefined>;\n}\n\nexport interface MiddlewareFlagInput extends NextZenmanageClientOptions {\n request: RequestLike;\n key: string;\n defaultValue?: FlagValue;\n defaults?: DefaultsCollection;\n requestContext?: BuildRequestContextOptions;\n}\n\nexport interface NextFlagsProviderProps extends Omit<FlagsProviderProps, 'defaults'> {\n defaults?: DefaultsCollection;\n /**\n * Flags evaluated on the server. Pass it as a prop so the server render and the first\n * client render see the same value, which is what keeps hydration clean.\n */\n bootstrapPayload?: BootstrapPayload | null;\n /** Read the payload from a `<script>` tag with this id when `bootstrapPayload` isn't set. */\n bootstrapElementId?: string;\n}\n\nexport interface NextFlagsRuntime {\n client: Zenmanage;\n manager: FlagManager;\n}\n","{\n \"name\": \"@zenmanage/nextjs\",\n \"version\": \"1.0.0\",\n \"description\": \"Next.js integration helpers for Zenmanage feature flags: server evaluation, middleware, and hydration-safe client bootstrap\",\n \"keywords\": [\n \"nextjs\",\n \"next.js\",\n \"react\",\n \"ssr\",\n \"app-router\",\n \"pages-router\",\n \"middleware\",\n \"feature-flags\",\n \"feature-toggles\",\n \"feature-management\",\n \"remote-config\",\n \"ab-testing\",\n \"experiments\",\n \"zenmanage\"\n ],\n \"homepage\": \"https://github.com/zenmanage/zenmanage-nextjs#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/zenmanage/zenmanage-nextjs/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zenmanage/zenmanage-nextjs.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Zenmanage\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n },\n \"./server\": {\n \"import\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n },\n \"require\": {\n \"types\": \"./dist/server.d.cts\",\n \"default\": \"./dist/server.cjs\"\n }\n },\n \"./middleware\": {\n \"import\": {\n \"types\": \"./dist/middleware.d.ts\",\n \"default\": \"./dist/middleware.js\"\n },\n \"require\": {\n \"types\": \"./dist/middleware.d.cts\",\n \"default\": \"./dist/middleware.cjs\"\n }\n },\n \"./client\": {\n \"import\": {\n \"types\": \"./dist/client.d.ts\",\n \"default\": \"./dist/client.js\"\n },\n \"require\": {\n \"types\": \"./dist/client.d.cts\",\n \"default\": \"./dist/client.cjs\"\n }\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"sideEffects\": false,\n \"scripts\": {\n \"build\": \"rm -rf dist && tsup\",\n \"clean\": \"rm -rf dist coverage\",\n \"changeset\": \"changeset\",\n \"version-packages\": \"changeset version\",\n \"release\": \"changeset publish\",\n \"lint\": \"eslint src tests --ext .ts,.tsx\",\n \"lint:fix\": \"eslint src tests --ext .ts,.tsx --fix\",\n \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx}\\\" \\\"tests/**/*.{ts,tsx}\\\" \\\"examples/**/*.{ts,tsx}\\\" \\\"examples/smoke-app/**/*.{js,jsx}\\\" \\\"scripts/**/*.mjs\\\" \\\"*.{md,json,yml,yaml,cjs}\\\"\",\n \"type-check\": \"tsc --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.examples.json\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:smoke\": \"npm run build && node scripts/smoke.mjs\",\n \"validate\": \"npm run format:check && npm run lint && npm run type-check && npm run test:coverage && npm run build && npm run test:smoke\",\n \"prepublishOnly\": \"npm run validate\"\n },\n \"peerDependencies\": {\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"next\": \"^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0\",\n \"react\": \"^18.2.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.28.1\",\n \"@testing-library/react\": \"^16.0.1\",\n \"@types/node\": \"^20.11.0\",\n \"@types/react\": \"^19.2.0\",\n \"@types/react-dom\": \"^19.2.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.20.0\",\n \"@typescript-eslint/parser\": \"^6.20.0\",\n \"@vitest/coverage-v8\": \"^1.6.0\",\n \"@zenmanage/react\": \"^1.0.0\",\n \"@zenmanage/sdk\": \"^3.5.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.2.1\",\n \"eslint-plugin-react-hooks\": \"^5.1.0\",\n \"jsdom\": \"^24.1.1\",\n \"next\": \"^15.5.0\",\n \"prettier\": \"^3.3.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.2.4\",\n \"typescript\": \"^5.6.2\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n","import { version } from '../package.json';\n\n/** The client agent the API registers this package under (see the api's EnsureClientAgentIsValid). */\nexport const CLIENT_AGENT = 'zenmanage-nextjs';\n\nexport const PACKAGE_VERSION: string = version;\n","import { DefaultsCollection } from '@zenmanage/sdk';\n\n/** Combines two collections. `base` wins where both define a key. */\nexport function mergeDefaults(\n base?: DefaultsCollection,\n bootstrap?: DefaultsCollection\n): DefaultsCollection | undefined {\n if (!base && !bootstrap) {\n return undefined;\n }\n\n const merged = new DefaultsCollection();\n\n for (const source of [bootstrap, base]) {\n if (!source) {\n continue;\n }\n\n for (const key of source.keys()) {\n const value = source.get(key);\n if (value !== undefined) {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n"],"mappings":"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","names":["DefaultsCollection","DefaultsCollection","DefaultsCollection"]}