{"_id":"@apdl-oss/sdk","_rev":"4-76326d96f915224aa5eb0a6ee5f5f2e3","name":"@apdl-oss/sdk","dist-tags":{"latest":"0.3.4"},"versions":{"0.1.0":{"name":"@apdl-oss/sdk","version":"0.1.0","keywords":["analytics","feature-flags","experiments","ab-testing","apdl"],"license":"MIT","_id":"@apdl-oss/sdk@0.1.0","maintainers":[{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"}],"homepage":"https://github.com/JahaanRawat/apdl/tree/main/sdk/javascript#readme","bugs":{"url":"https://github.com/JahaanRawat/apdl/issues"},"dist":{"shasum":"3bf7f9e122058567ca491c86dccaad0af66d8a25","tarball":"https://registry.npmjs.org/@apdl-oss/sdk/-/sdk-0.1.0.tgz","fileCount":79,"integrity":"sha512-5QKDYn228N2AKqlcOItA48KUjGC0+zp3VqRTcVx9k3bg1rvPPOmYYH2EQl6ALXWcV1chpCvcsgONCz8RLDQDDg==","signatures":[{"sig":"MEUCICe4la+2Aq7yYfodP8aT8PHymuQsO/SeIMe+qxSuQrDCAiEAwlC7vBGxVK9CWCeTMTd3FnCnmH6OsfwfFikZFCjCu7Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1315911},"main":"dist/apdl.cjs.js","type":"module","types":"dist/index.d.ts","module":"dist/apdl.esm.js","browser":"dist/apdl.iife.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/apdl.esm.js","require":"./dist/apdl.cjs.js"}},"gitHead":"a66f50f0881286549f981043da63be1e71210c67","scripts":{"lint":"npm run typecheck && tsc -p __tests__/tsconfig.json --noEmit","test":"vitest run","build":"rollup -c rollup.config.ts --configPlugin typescript","clean":"rm -rf dist","setup":"npm ci","prepack":"npm run build","typecheck":"tsc --noEmit","test:watch":"vitest","pack:dry-run":"npm pack --dry-run","release:check":"npm run lint && npm test && npm run build && npm run pack:dry-run","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"},"repository":{"url":"git+https://github.com/JahaanRawat/apdl.git","type":"git","directory":"sdk/javascript"},"_npmVersion":"10.5.0","description":"Client SDK for the Autonomous Product Development Loop platform","directories":{},"sideEffects":false,"_nodeVersion":"21.7.3","dependencies":{"web-vitals":"^5.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","tslib":"^2.6.0","rollup":"^4.12.0","vitest":"^1.3.0","typescript":"^5.4.0","@types/node":"^20.19.42","@vitest/coverage-v8":"^1.3.0","@rollup/plugin-terser":"^0.4.4","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.0_1781313457947_0.19033492283092945","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@apdl-oss/sdk","version":"0.2.0","keywords":["analytics","feature-flags","experiments","ab-testing","apdl"],"license":"MIT","_id":"@apdl-oss/sdk@0.2.0","maintainers":[{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"}],"homepage":"https://github.com/kuvera-apdl/apdl/tree/main/sdk/javascript#readme","bugs":{"url":"https://github.com/kuvera-apdl/apdl/issues"},"dist":{"shasum":"fb6dc004a54ae0e11a3939425fef4506e752217e","tarball":"https://registry.npmjs.org/@apdl-oss/sdk/-/sdk-0.2.0.tgz","fileCount":79,"integrity":"sha512-3x8M2g7FPyV37bpmBGsNmrb0lNgmz9rF4bpg/JY/mGJIthjNMrTXusNxh2kZoZNn4iPG88tr9BDaobYXtI6LCg==","signatures":[{"sig":"MEQCIDlJw20Xl3M4N+LTfzlLfruYM9eWo7qyplk8/4KPXvvbAiB085RHr0Pq1EP8SeIngJ2SQh8pQlFLPr3NMIiWdML0wQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1313746},"main":"dist/apdl.cjs.js","type":"module","types":"dist/index.d.ts","module":"dist/apdl.esm.js","browser":"dist/apdl.iife.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/apdl.esm.js","require":"./dist/apdl.cjs.js"}},"gitHead":"4eee2ade5d851acf01c00a5e22e911bebfb67814","scripts":{"lint":"npm run typecheck && tsc -p __tests__/tsconfig.json --noEmit","test":"vitest run","build":"rollup -c rollup.config.ts --configPlugin typescript","clean":"rm -rf dist","setup":"npm ci","prepack":"npm run build","typecheck":"tsc --noEmit","test:watch":"vitest","pack:dry-run":"npm pack --dry-run","release:check":"npm run lint && npm test && npm run build && npm run pack:dry-run","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"},"repository":{"url":"git+https://github.com/kuvera-apdl/apdl.git","type":"git","directory":"sdk/javascript"},"_npmVersion":"10.5.0","description":"Client SDK for the Autonomous Product Development Loop platform","directories":{},"sideEffects":false,"_nodeVersion":"21.7.3","dependencies":{"web-vitals":"^5.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","tslib":"^2.6.0","rollup":"^4.12.0","vitest":"^1.3.0","typescript":"^5.4.0","@types/node":"^20.19.42","@vitest/coverage-v8":"^1.3.0","@rollup/plugin-terser":"^0.4.4","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.2.0_1781574367485_0.4798993351518661","host":"s3://npm-registry-packages-npm-production"}},"0.3.3":{"name":"@apdl-oss/sdk","version":"0.3.3","keywords":["analytics","feature-flags","experiments","ab-testing","apdl"],"license":"MIT","_id":"@apdl-oss/sdk@0.3.3","maintainers":[{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"}],"homepage":"https://github.com/kuvera-apdl/apdl/tree/main/sdk/javascript#readme","bugs":{"url":"https://github.com/kuvera-apdl/apdl/issues"},"dist":{"shasum":"dd357ebddb6e698aaf4754a712a7a3bc6ffc2474","tarball":"https://registry.npmjs.org/@apdl-oss/sdk/-/sdk-0.3.3.tgz","fileCount":148,"integrity":"sha512-liqhJtaBlefODeSCfpm3/P+9F4C6s5XiEsyl0WjbfcEcZRknYOpqlhU8RKWsrKnUfg3sG4C5HNwE/bbMTFj8zw==","signatures":[{"sig":"MEQCIE0jQGb8auTTWFlJNHJxMuyVptRpEoFvRJjn7YTObKE8AiA3t6N9+YRZy9VewJ/NJXT7rOjtmzk4eZzoJsZjL3t2Ow==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2543890},"main":"dist/apdl.cjs","type":"module","_from":"file:/Users/kirillsukhikh/APDL/APDL-OSS/release-artifacts-v0.3.3.9NOl7I/npm/apdl-oss-sdk-0.3.3.tgz","types":"dist/index.d.ts","module":"dist/apdl.esm.js","browser":"dist/apdl.iife.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/apdl.esm.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/apdl.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react.esm.js"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react.cjs"}}},"scripts":{"lint":"npm run typecheck && tsc -p __tests__/tsconfig.json --noEmit","test":"vitest run","build":"npm run clean && rollup -c rollup.config.ts --configPlugin typescript && node scripts/normalize-declarations.mjs","clean":"rm -rf dist","setup":"npm ci","prepack":"npm run build","typecheck":"tsc --noEmit","test:watch":"vitest","lint:package":"publint","pack:dry-run":"npm pack --dry-run","release:check":"npm run lint && npm test && npm run build && npm run test:built-browser && npm run lint:package && npm run pack:dry-run","test:coverage":"vitest run --coverage","test:built-browser":"node scripts/test-built-browser.mjs && node scripts/test-browser-lifecycle.mjs","test:browser-lifecycle":"node scripts/test-browser-lifecycle.mjs"},"_npmUser":{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"},"_resolved":"/Users/kirillsukhikh/APDL/APDL-OSS/release-artifacts-v0.3.3.9NOl7I/npm/apdl-oss-sdk-0.3.3.tgz","_integrity":"sha512-liqhJtaBlefODeSCfpm3/P+9F4C6s5XiEsyl0WjbfcEcZRknYOpqlhU8RKWsrKnUfg3sG4C5HNwE/bbMTFj8zw==","repository":{"url":"git+https://github.com/kuvera-apdl/apdl.git","type":"git","directory":"sdk/javascript"},"_npmVersion":"10.9.8","description":"Client SDK for the Autonomous Product Development Loop platform","directories":{},"sideEffects":["./dist/apdl.esm.js","./dist/apdl.cjs","./dist/apdl.iife.js"],"_nodeVersion":"22.23.2","dependencies":{"web-vitals":"^5.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.1.1","react":"19.2.7","tslib":"^2.6.0","rollup":"^4.12.0","vitest":"4.1.10","publint":"0.3.21","react-dom":"19.2.7","typescript":"^5.4.0","@types/node":"^26.1.1","@types/react":"19.2.17","fake-indexeddb":"6.2.5","@types/react-dom":"19.2.3","@vitest/coverage-v8":"4.1.10","@rollup/plugin-terser":"1.0.0","@rollup/plugin-replace":"6.0.3","@testing-library/react":"16.3.2","@rollup/plugin-typescript":"^12.3.0","@rollup/plugin-node-resolve":"^16.0.3"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.3.3_1785352573117_0.8714745545121025","host":"s3://npm-registry-packages-npm-production"}},"0.3.4":{"name":"@apdl-oss/sdk","version":"0.3.4","description":"Client SDK for the Autonomous Product Development Loop platform","keywords":["analytics","feature-flags","experiments","ab-testing","apdl"],"repository":{"type":"git","url":"git+https://github.com/kuvera-apdl/apdl.git","directory":"sdk/javascript"},"homepage":"https://github.com/kuvera-apdl/apdl/tree/main/sdk/javascript#readme","bugs":{"url":"https://github.com/kuvera-apdl/apdl/issues"},"type":"module","main":"dist/apdl.cjs","module":"dist/apdl.esm.js","browser":"dist/apdl.iife.js","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/apdl.esm.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/apdl.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react.esm.js"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react.cjs"}}},"publishConfig":{"access":"public"},"scripts":{"setup":"npm ci","clean":"rm -rf dist","build":"npm run clean && rollup -c rollup.config.ts --configPlugin typescript && node scripts/normalize-declarations.mjs","test":"vitest run","test:built-browser":"node scripts/test-built-browser.mjs && node scripts/test-browser-lifecycle.mjs","test:browser-lifecycle":"node scripts/test-browser-lifecycle.mjs","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","lint":"npm run typecheck && tsc -p __tests__/tsconfig.json --noEmit","lint:package":"publint","pack:dry-run":"npm pack --dry-run","release:check":"npm run lint && npm test && npm run build && npm run test:built-browser && npm run lint:package && npm run pack:dry-run","prepack":"npm run build"},"dependencies":{"web-vitals":"^5.3.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"devDependencies":{"@rollup/plugin-node-resolve":"^16.0.3","@rollup/plugin-replace":"6.0.3","@rollup/plugin-terser":"1.0.0","@rollup/plugin-typescript":"^12.3.0","@testing-library/react":"16.3.2","@types/node":"^26.1.1","@types/react":"19.2.17","@types/react-dom":"19.2.3","@vitest/coverage-v8":"4.1.10","fake-indexeddb":"6.2.5","jsdom":"29.1.1","publint":"0.3.21","react":"19.2.7","react-dom":"19.2.7","rollup":"^4.12.0","tslib":"^2.6.0","typescript":"^5.4.0","vitest":"4.1.10"},"sideEffects":["./dist/apdl.esm.js","./dist/apdl.cjs","./dist/apdl.iife.js"],"license":"MIT","_id":"@apdl-oss/sdk@0.3.4","_integrity":"sha512-Tmpw55IH2jy87jQCmqjXUGX++mi3wSsvR5O4htS1SKkzqJXQIaGR9WZGSqsnTKz1EnuVuQaJ6AqdJcSAMlG93A==","_resolved":"/var/folders/cp/lq514fks14xb4ms2lj82sxs40000gn/T/apdl-npm-0.3.4.L6dMUv/apdl-oss-sdk-0.3.4.tgz","_from":"file:/var/folders/cp/lq514fks14xb4ms2lj82sxs40000gn/T/apdl-npm-0.3.4.L6dMUv/apdl-oss-sdk-0.3.4.tgz","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-Tmpw55IH2jy87jQCmqjXUGX++mi3wSsvR5O4htS1SKkzqJXQIaGR9WZGSqsnTKz1EnuVuQaJ6AqdJcSAMlG93A==","shasum":"4544eeb4aa845626405e23fda414912e4f4f7264","tarball":"https://registry.npmjs.org/@apdl-oss/sdk/-/sdk-0.3.4.tgz","fileCount":148,"unpackedSize":2546605,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDuXtNwl25QgwgpD8TYBckYEEjRBNNirHB9qrNPy+Ea5gIhAPskaFGbGA2Ip9eRlVdnrZdMGXGGrJNXMAFjHvjG8UYa"}]},"_npmUser":{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"},"directories":{},"maintainers":[{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.3.4_1785373726711_0.869839422358923"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-13T01:17:37.777Z","modified":"2026-07-30T01:08:47.060Z","0.1.0":"2026-06-13T01:17:38.153Z","0.2.0":"2026-06-16T01:46:07.726Z","0.3.3":"2026-07-29T19:16:13.308Z","0.3.4":"2026-07-30T01:08:46.904Z"},"bugs":{"url":"https://github.com/kuvera-apdl/apdl/issues"},"license":"MIT","homepage":"https://github.com/kuvera-apdl/apdl/tree/main/sdk/javascript#readme","keywords":["analytics","feature-flags","experiments","ab-testing","apdl"],"repository":{"type":"git","url":"git+https://github.com/kuvera-apdl/apdl.git","directory":"sdk/javascript"},"description":"Client SDK for the Autonomous Product Development Loop platform","maintainers":[{"name":"sukkirill","email":"kirillsukhikh99@gmail.com"}],"readme":"# @apdl-oss/sdk\n\nBrowser TypeScript SDK for the **Autonomous Product Development Loop** platform.\nThe SDK sends product analytics events to the ingestion service, evaluates\nfeature flag variants client-side, receives real-time configuration updates from\nthe config service over SSE, provides a local UI renderer, and exposes\nexperiment context for flag targeting. It uses the same FNV-1a bucketing as the\nPython SDK and the config service, so a user buckets identically no matter where\na flag is evaluated.\n\n- 🪄 Auto-capture: page views, clicks, form submissions, scroll depth, rage\n  clicks, frontend errors, web vitals\n- 🚩 Local feature flag variant evaluation (no network round-trip on the hot path)\n- 🔁 Real-time flag updates over SSE, with a persisted local flag cache\n- 🧩 Local UI component renderer (backend UI-config delivery is not in 0.3.0)\n- 🔒 Privacy controls: consent management, PII scrubbing, cookieless mode\n- ⚛️ First-party React/Next adapter (`@apdl-oss/sdk/react`) — a provider + hook, no wrapper boilerplate\n- 🧯 Zero-config setup: env conventions, SSR-safe init, idempotent singleton, fail-soft validation\n- 📦 Ships ESM, CJS, and an IIFE browser bundle, with full TypeScript types\n\n## Installation\n\n```bash\nnpm install @apdl-oss/sdk\n```\n\nOr drop the IIFE bundle into any page (exposes a global `APDL`):\n\n```html\n<script src=\"https://unpkg.com/@apdl-oss/sdk/dist/apdl.iife.js\"></script>\n```\n\n## Initialization\n\n```typescript\nimport { APDL } from '@apdl-oss/sdk';\n\nconst apdl = APDL.init({\n  endpoint: 'https://api.example.com',\n  auth: {\n    clientKey: 'client_demo_0123456789abcdef',\n  },\n  autoCapture: true,\n  privacyMode: 'standard',\n});\n```\n\n`APDL.init(config)` (also exported as the bare `init(config)`) is the primary\npublic entrypoint. It is:\n\n- **SSR-safe** — on the server (no `window`) it returns an inert no-op client\n  and opens no sockets, timers, or fetches, so it is safe to call at module\n  scope in frameworks like Next.js.\n- **An idempotent singleton** — repeated calls with the same `clientKey` return\n  the same client, so it is immune to React StrictMode double-invoke and HMR\n  re-runs (no duplicate listeners, SSE connections, or flush loops). The\n  instance is evicted on `shutdown()`, so a later `init()` starts fresh.\n- **Fail-soft** — when `endpoint`/`clientKey` are absent it warns once and\n  returns a no-op client instead of throwing, so an unset env var does not crash\n  every route. Malformed values (bad key format, removed fields) still throw.\n\n### Zero-config setup (env conventions)\n\nIf `endpoint` / `auth.clientKey` are omitted, they are read from environment\nvariables, so `init()` can be called with no arguments:\n\n| Field | Browser (bundler-inlined) | Server |\n|---|---|---|\n| endpoint | `NEXT_PUBLIC_APDL_URL` | `APDL_URL` |\n| clientKey | `NEXT_PUBLIC_APDL_CLIENT_KEY` | `APDL_CLIENT_KEY` |\n\nFor Next.js, add the browser-safe values to `.env.local` and restart the\ndevelopment server:\n\n```dotenv\nNEXT_PUBLIC_APDL_URL=https://api.example.com\nNEXT_PUBLIC_APDL_CLIENT_KEY=client_demo_0123456789abcdef\n```\n\nThe SDK uses direct, statically analyzable references to these public variables,\nso Next.js includes them in the browser bundle. Never put a secret server key in\na `NEXT_PUBLIC_*` variable.\n\nFor module-scope use without any `useEffect`, import the lazy `apdl` singleton.\nIt no-ops on the server and auto-starts on the first browser tick, reading config\nfrom the env conventions above:\n\n```typescript\nimport { apdl } from '@apdl-oss/sdk'; // no 'use client', no useEffect\n\napdl.track('cta_clicked', { id: 'hero' });\nconst variant = apdl.getVariant('new-checkout-flow');\n```\n\n## React & Next.js\n\nInstall the package and drop the provider in once — it owns the `'use client'`\nboundary, the singleton lifecycle, and SSR safety internally:\n\n```tsx\n// app/layout.tsx — the entire integration\nimport { APDLProvider } from '@apdl-oss/sdk/react';\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return <APDLProvider autoCapture>{children}</APDLProvider>;\n}\n```\n\nWith `NEXT_PUBLIC_APDL_URL` / `NEXT_PUBLIC_APDL_CLIENT_KEY` set, the example above\nis a complete setup. You can also pass props explicitly\n(`<APDLProvider endpoint={...} clientKey={...} autoCapture>`).\n\nRead the client anywhere with the `useAPDL` hook — no instance threading:\n\n```tsx\nimport { useAPDL } from '@apdl-oss/sdk/react';\n\nfunction HeroCTA() {\n  const apdl = useAPDL();\n  const variant = apdl.getVariant('new-checkout-flow');\n  return <button onClick={() => apdl.track('cta_clicked', { id: 'hero' })}>Buy</button>;\n}\n```\n\n`react` (>= 18) is an optional peer dependency, required only when importing\n`@apdl-oss/sdk/react`. Outside a provider, `useAPDL()` returns an inert no-op\nclient, so calls never throw.\n\n## Config Fields\n\nThe SDK uses one initialization contract:\n\n| Field | Required | Description |\n|---|---:|---|\n| `endpoint` | Yes¹ | Absolute HTTP(S) origin of the APDL gateway, with no credentials, path, query, or fragment. The SDK posts events to `/v1/events` and reads flags + SSE from `/v1/flags` and `/v1/stream` on this one origin. |\n| `auth.clientKey` | Yes¹ | Browser-safe APDL client key used for service authentication and project identification. |\n\n¹ Resolved from the env conventions above when omitted. If still absent, `init()`\nreturns a no-op client (fail-soft); `new APDLClient(config)` and\n`resolveConfig(config, { strict: true })` throw.\n\n`auth.clientKey` must use the canonical APDL client key format:\n\n```text\nclient_{project_id}_{token}\n```\n\nThe token must be 16+ alphanumeric characters. The SDK derives the project ID\nfrom the client key internally. Do not pass `projectId`, `apiKey`, `host`,\n`configHost`, or the old `endpoints` object; those fields are not part of the\npublic config contract and the SDK rejects them.\n\nOptional fields include:\n\n| Field | Description |\n|---|---|\n| `autoCapture` | `true`, `false`, or a per-signal capture config. |\n| `batchSize` | Integer events per batch, from 1 through 100. |\n| `flushInterval` | Integer queue flush interval from 100 through 3,600,000 milliseconds. |\n| `privacyMode` | `'standard'` or `'cookieless'`. |\n| `consent` | Initial consent state for `analytics`, `personalization`, and `experiments`. |\n| `persistence` | `'localStorage'` for project-scoped browser storage or `'memory'` for no browser storage. |\n| `maxQueueSize` | Integer maximum from 1 through 100,000 events owned in memory. A new event is rejected synchronously when full; an already accepted event is never evicted to make room. |\n| `debug` | Enables SDK diagnostics when `true`. |\n\nConfiguration is validated at runtime for JavaScript and parsed-JSON callers.\nUnknown fields, malformed types, non-finite or fractional numeric values,\nout-of-range values, and unsupported enum members fail during initialization.\nThe former `persistence: 'cookie'` and `privacyMode: 'strict'` values are not\nimplemented and are rejected instead of being mapped to different behavior.\n\nAutomatic events use fixed property allowlists enforced before and after custom\nscrubbers. Clicks contain only bounded tag and coordinate metadata; form submits\ncontain only the HTTP method; input changes contain only tag, type, and a value\npresence boolean. DOM text, form values/actions/names/IDs, CSS metadata, page\ntitles, query strings, fragments, and full referrers are not collected. Browser\ncontext contains a query-free HTTP(S) URL and path, while click and rage-click\ncontext omits page location entirely. Known credential, one-time-code, file,\nand payment controls identified from native types and semantic hints are\nexcluded from click capture. Use manual events when an application needs an\nexplicitly chosen semantic label.\n\n## Local Development Endpoints\n\nWhen running the local APDL services, initialize the SDK with the local\ngateway URL (`make dev-core` starts the gateway on port 8000):\n\n```typescript\nconst apdl = APDL.init({\n  endpoint: 'http://localhost:8000',\n  auth: {\n    clientKey: 'client_demo_0123456789abcdef',\n  },\n  autoCapture: true,\n  privacyMode: 'standard',\n});\n```\n\nStart the local services from the repository root:\n\n```bash\nmake run-ingestion\nmake run-config\n```\n\n## Event Tracking\n\n```typescript\napdl.track('purchase_completed', {\n  product_id: 'sku-123',\n  revenue: 49.99,\n});\n\napdl.page('Pricing', {\n  path: '/pricing',\n});\n```\n\nEvents are batched and sent to the gateway `endpoint` at `/v1/events`.\nProperties, traits, and custom context must be canonical JSON: finite numbers,\nstrings, booleans, nulls, arrays, and plain string-keyed objects. Cycles,\n`BigInt`, accessors, sparse arrays, unsupported values, malformed timestamps,\nunknown context fields, excessive nesting/cardinality, and events over 64 KiB\nare rejected synchronously before queue ownership. Requests are split below\n512 KiB. Event timestamps may be at most seven days old and at most five\nminutes ahead of the browser clock, matching the ingestion and offline-storage\nwindow; the SDK rejects out-of-window time instead of rewriting it. Network\nerrors, HTTP 408/425/429, and 5xx retain the same stable\nmessage IDs for retry; other non-2xx responses are permanent and cannot poison\nlater queue entries. If both a retryable send and offline persistence fail, the\nbatch is requeued once in memory and returned in the drain's `pending` report;\nthe SDK does not spin or silently discard it. IndexedDB overflow is also\nexplicit: each count/byte eviction is returned in `DeliveryReport.dropped`\nwith the evicted event and one canonical reason.\n\n## User Identification\n\n```typescript\napdl.identify('user-42', {\n  email: 'user@example.com',\n  plan: 'pro',\n});\n\napdl.group('account-7', {\n  tier: 'enterprise',\n});\n\napdl.reset();\n```\n\nIdentified user traits participate in feature flag evaluation.\n\n`identify(userId)` keeps the current project-scoped anonymous ID on the\ncanonical `identify` event and on later events. An `identify` event containing\nboth IDs is the only anonymous-to-user alias assertion; there is no separate\nalias event or `previous_id` field. `reset()` clears the user, rotates the\nanonymous ID, and does not undo the historical relationship for the old ID.\nAccepted assertions are irreversible; the wire contract has no unmerge event.\n\nAlias-backed analytics converge asynchronously: the relationship becomes\nquery-visible after the ingestion writer durably stores the identify event, at\nwhich point earlier retained events with that project and anonymous ID resolve\nto the identified user. Calls made without analytics consent do not emit an\nalias assertion. Conflicting user claims for one anonymous ID fail closed and\nremain separate actors until an operator rebuilds the alias state.\n\n## Feature Flags\n\n```typescript\nconst variant = apdl.getVariant('new-checkout-flow');\n\nif (variant === 'treatment') {\n  renderTreatmentCheckout();\n}\n```\n\nFor diagnostics, use `getVariantDetails`:\n\n```typescript\nconst result = apdl.getVariantDetails('new-checkout-flow', {\n  page: '/checkout',\n  component: 'checkout-form',\n});\n\nconsole.log(result.variant, result.reason);\n```\n\nFlag evaluation automatically emits a deduplicated `$feature_flag_exposure`\nevent. The SDK fetches initial flag configuration from the gateway `endpoint` at\n`/v1/flags` and listens for real-time updates on `/v1/stream`. The SSE request\nuses `X-API-Key` header authentication through a fetch stream; the client key is\nnever placed in a URL. Reconnects resume with the standard `Last-Event-ID`\nheader.\n\nReact to real-time variant changes pushed over SSE:\n\n```typescript\nconst unsubscribe = apdl.onVariantChange('new-checkout-flow', (variant) => {\n  rerenderCheckout(variant);\n});\n// later: unsubscribe();\n```\n\n## Experiment Context\n\nUse the `experiments` namespace to provide stable targeting attributes for flag\nevaluation:\n\n```typescript\napdl.experiments.setContext({\n  attributes: {\n    plan: 'pro',\n    region: 'us',\n  },\n});\n\nconst context = apdl.experiments.getContext();\n\napdl.experiments.clearContext();\n```\n\nExperiment context must use the canonical shape\n`{ attributes: Record<string, unknown> }`. These attributes are merged into the\nfeature flag evaluation context and may be included in feature flag exposure\nevent metadata.\n\n## Privacy & Consent\n\n```typescript\n// Inspect or update consent at runtime (e.g. from a cookie banner)\napdl.consent.get();\napdl.consent.update({ analytics: false });\napdl.consent.onUpdate((state) => console.log('consent changed', state));\n\n// Register or remove custom PII scrubbers applied to every outgoing event\nconst scrubSsn = (event) => {\n  delete event.properties?.ssn;\n  return event;\n};\napdl.privacy.addScrubber(scrubSsn);\napdl.privacy.removeScrubber(scrubSsn);\n```\n\nBaseline email, payment-card, and SSN scrubbers run in every privacy mode.\n`privacyMode: 'cookieless'` additionally derives a daily-rotating anonymous ID\nwithout persisting that identifier.\n\nRevoking analytics consent is an immediate delivery fence: the SDK aborts the\nactive analytics request when possible, clears its in-memory queue and this\nproject's IndexedDB queue, and stops analytics auto-capture and health capture.\nNo retained event is restored or sent across a revoke/regrant boundary.\nRegranting consent starts capture again for new events only.\n\nExperiment consent is also fail-closed. Denial returns a `null` assignment with\nreason `consent_denied`, suppresses and removes exposures, clears experiment\ncontext and flag caches, and prevents the initial flag fetch and SSE stream.\nRegranting starts from a fresh authoritative flag snapshot. Personalization\ndenial prevents slot discovery and rendering and removes already rendered SDK\ncomponents; regranting resumes discovery for application-owned UI configs.\n\nWith `persistence: 'localStorage'`, browser persistence is project-scoped.\nAnonymous identity, session, consent, flag cache, and offline event records use\nthe project ID derived from the client key, so two APDL projects on one origin\ncannot restore each other's state. `persistence: 'memory'` does not read or\nwrite localStorage and does not open IndexedDB; all state ends with the client.\nRetryable delivery failures therefore remain in `DeliveryReport.pending` for\nan explicit same-session retry instead of being reported as persisted.\n\nWith `persistence: 'localStorage'`, failed analytics deliveries may be retained\nin IndexedDB for up to seven days. Restore takes a five-minute client lease\nwithout deleting the record; deletion occurs only after an accepted or\npermanently rejected server response. Retryable failures release the lease,\ncrashed-client leases are reclaimable after expiry, and a stale client cannot\nacknowledge a record reclaimed by another tab.\nEach record is scoped to the canonical project ID derived from the client key;\nthe key itself is never persisted. A client cannot drain or clear another\nproject's records on the same origin, and current analytics consent is checked\nagain before any retained event is restored. Legacy, invalid, and expired\nrecords are discarded. Each project retains at most the newest 1,000 events and\n5 MiB of UTF-8 JSON event payloads; older records are evicted deterministically\nwithout counting or deleting another project's records. Active leases are\nnever evicted. Every overflow or invalid-storage rejection is returned in the\nimmutable delivery report rather than counted as persisted. A single oversized\nor non-JSON-serializable event is not retained.\n\n## Local UI Renderer (No 0.3.0 Backend Delivery)\n\nThe package includes component registration, rendering, and slot-discovery\nutilities. APDL 0.3.0 does not have a canonical Config UI-config endpoint and\ndoes not publish UI configurations over SSE, so applications must pass a\nlocally owned `UIConfig` to `apdl.ui.render(...)`. The Agents personalization\ngraph is disabled for the same reason.\n\n```typescript\n// Register a custom component\napdl.ui.register({\n  type: 'countdown-banner',\n  schema: { properties: { deadline: { type: 'string' } } },\n  render: (props, ctx) => { /* return an HTMLElement */ },\n});\n\napdl.ui.render(locallyOwnedConfig, document.querySelector('#offer')!);\n\n// React when the SDK discovers a UI slot on the page\napdl.ui.onSlotUpdate((slotId, element) => { /* ... */ });\n```\n\n## Debugging & Shutdown\n\n```typescript\napdl.debug.enable();          // verbose console logging\napdl.debug.getQueue();        // inspect queued events\nconst report = await apdl.debug.flush();\n\nconsole.log(report.delivered, report.persisted);\nconsole.log(report.permanentRejections, report.pending);\nconsole.log(report.dropped);\n\nconst finalReport = await apdl.shutdown();\n```\n\n`flush()` drains all currently owned in-memory events, and concurrent flushes\njoin the same operation. Its frozen `DeliveryReport` distinguishes delivered,\noffline-persisted, permanently rejected, consent-discarded, and still-pending\nevents. Its `dropped` entries separately identify offline evictions and invalid\nstorage rejections by stable event `messageId`; `persisted` counts only records\nthat survived in durable IndexedDB. The exact eviction reasons are\n`offline_count_limit` and `offline_byte_limit`; an invalid storage candidate is\nreported as `offline_invalid_event`. `shutdown()` stops accepting tracking immediately, joins concurrent\ncallers, tears down capture and SSE, and returns the final drain report. Calls\nto `track`, `identify`, `group`, `page`, or `reset` after shutdown throw.\n\n## SDK Development\n\nRun SDK commands from `sdk/javascript`:\n\n```bash\nnpm run setup\nnpm test\nnpm run lint\nnpm run build\nnpm run release:check\n```\n\nOr use the repository-level make targets:\n\n```bash\nmake setup-sdk\nmake test-sdk\nmake lint-sdk\nmake build-sdk\nmake release-sdk\n```\n\n`npm run lint` runs the strict `tsc` typecheck (the lint gate), `npm run build`\nproduces the ESM, CJS, and IIFE bundles in `dist/`, and `npm run release:check`\nruns linting, tests, build, and an npm package dry run. Tests live in\n`__tests__/**/*.test.ts`; the flag-evaluation suite pins golden hash values from\nthe canonical config-service implementation, guaranteeing this SDK buckets\nidentically to the server and the Python SDK.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}