{"_id":"@assistshub/ah-ff-react","name":"@assistshub/ah-ff-react","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@assistshub/ah-ff-react","version":"0.1.0","description":"React SDK for AssistsHub CMS feature flags (GraphQL snapshot + Phoenix WebSocket live updates)","license":"MIT","author":{"name":"Bielorusov","email":"assistshub@gmail.com","url":"https://assistshub.com"},"repository":{"type":"git","url":"git+https://github.com/bielorusov/ah-ff-react.git"},"homepage":"https://github.com/bielorusov/ah-ff-react#readme","bugs":{"url":"https://github.com/bielorusov/ah-ff-react/issues"},"keywords":["feature-flags","react","assistshub","phoenix","graphql"],"sideEffects":false,"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"publishConfig":{"access":"public"},"scripts":{"build":"tsup","typecheck":"tsc --noEmit","test":"vitest run","test:coverage":"vitest run --coverage","test:watch":"vitest","prepublishOnly":"npm run build"},"peerDependencies":{"phoenix":"^1.7.0","react":">=18.0.0","react-dom":">=18.0.0"},"devDependencies":{"@testing-library/dom":"^10.4.1","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.0","@types/phoenix":"^1.6.6","@types/react":"^19.1.8","@types/react-dom":"^19.1.6","@vitest/coverage-v8":"^3.2.4","jsdom":"^26.1.0","phoenix":"^1.7.21","react":"^19.1.0","react-dom":"^19.1.0","tsup":"^8.5.0","typescript":"^5.8.3","vitest":"^3.2.4"},"gitHead":"47d266250e6f549d8bde5b72109832a07d1a5826","_id":"@assistshub/ah-ff-react@0.1.0","_nodeVersion":"24.12.0","_npmVersion":"11.7.0","dist":{"integrity":"sha512-cnn8Xc3oPkkrkJrJjbW61J233ztsCBai3z5YFauk4KODEs5BEx4BGBT8uK+np88mYE6H7GJ0Kc7UQa5XzRbP8A==","shasum":"6c8ee99258618c09838d816ef182283ee2fe521e","tarball":"https://registry.npmjs.org/@assistshub/ah-ff-react/-/ah-ff-react-0.1.0.tgz","fileCount":9,"unpackedSize":129910,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD7HjiA/KPKuMkkom55rEnmHYpkXI4OgDEWuexHiY9OmgIget4vCbpqtz1WBFZzeG+PUDIw4HRy1MdMvSS64ISfWPc="}]},"_npmUser":{"name":"assistshub","email":"assistshub@gmail.com"},"directories":{},"maintainers":[{"name":"assistshub","email":"assistshub@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ah-ff-react_0.1.0_1784005543302_0.4871753893013444"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-14T05:05:43.154Z","0.1.0":"2026-07-14T05:05:43.433Z","modified":"2026-07-14T05:05:43.575Z"},"maintainers":[{"name":"assistshub","email":"assistshub@gmail.com"}],"description":"React SDK for AssistsHub CMS feature flags (GraphQL snapshot + Phoenix WebSocket live updates)","homepage":"https://github.com/bielorusov/ah-ff-react#readme","keywords":["feature-flags","react","assistshub","phoenix","graphql"],"repository":{"type":"git","url":"git+https://github.com/bielorusov/ah-ff-react.git"},"author":{"name":"Bielorusov","email":"assistshub@gmail.com","url":"https://assistshub.com"},"bugs":{"url":"https://github.com/bielorusov/ah-ff-react/issues"},"license":"MIT","readme":"# ah-ff-react\n\nReact SDK for AssistsHub CMS **feature flags**: GraphQL snapshot bootstrap + Phoenix WebSocket live updates, with reconnect-aware GraphQL polling fallback and optional `localStorage` cache.\n\n## Install\n\n```bash\nnpm install ah-ff-react phoenix\n```\n\n### Peer dependencies\n\n| Package     | Version   |\n|-------------|-----------|\n| `react`     | `>=18`    |\n| `react-dom` | `>=18`    |\n| `phoenix`   | `^1.7.0`  |\n\nCompatible with React 18 and React 19.\n\n## Quick start\n\n```tsx\nimport { FeatureFlagsProvider, useFeatureFlags, useBooleanFlag } from 'ah-ff-react'\n\nfunction App() {\n  return (\n    <FeatureFlagsProvider\n      config={{\n        clientKey: import.meta.env.VITE_AH_FF_CLIENT_KEY,\n        env: import.meta.env.VITE_AH_FF_CLIENT_ENV ?? 'development',\n        // CMS host (open CORS on external feature-flags API) or '' for same-origin / Vite proxy:\n        baseUrl: '',\n        cache: true,\n      }}\n    >\n      <FlagsBanner />\n    </FeatureFlagsProvider>\n  )\n}\n\nfunction FlagsBanner() {\n  const { flags, connected, status, error, loading, refresh } = useFeatureFlags()\n  const emailSignupEnabled = useBooleanFlag('FF_SIGN_UP_BY_EMAIL_ENABLED')\n\n  if (loading) return <p>Loading flags…</p>\n  if (error) return <p>Error: {error}</p>\n\n  return (\n    <div>\n      <p>WS: {connected ? 'connected' : status}</p>\n      <p>Email signup: {emailSignupEnabled ? 'on' : 'off'}</p>\n      <ul>\n        {flags.map((flag) => (\n          <li key={flag.name}>\n            {flag.name}: {String(flag.value)} ({flag.valueType})\n          </li>\n        ))}\n      </ul>\n      <button type=\"button\" onClick={() => void refresh()}>\n        Refresh\n      </button>\n    </div>\n  )\n}\n```\n\n### Env vars (example)\n\n```bash\nVITE_AH_FF_CLIENT_KEY=ff-your-client-key\nVITE_AH_FF_CLIENT_ENV=development\n```\n\n## Configuration\n\n```ts\nimport { createConfig, FeatureFlagsProvider } from 'ah-ff-react'\n\nconst config = createConfig({\n  clientKey: 'ff-…',\n  env: 'development', // 'development' | 'test' | 'production'\n  baseUrl: 'https://cms.assistshub.com', // or '' for same-origin\n  socketUrl: undefined, // derived from baseUrl when omitted\n  autoConnect: true,\n  pollingIntervalMs: 30_000,\n  enablePollingFallback: true,\n  cache: true, // or { storageKey: 'my-app:ff' }\n})\n\n;<FeatureFlagsProvider config={config}>{/* … */}</FeatureFlagsProvider>\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `clientKey` | — | Required `X-FF-Client-Key` / socket `client_key` |\n| `env` | — | `development` \\| `test` \\| `production` |\n| `baseUrl` | `https://cms.assistshub.com` | HTTP origin used for GraphQL (`…/api/v1/external/feature-flags/graphql`) |\n| `socketUrl` | derived (`http→ws`, `https→wss`) | Phoenix endpoint **without** `/websocket` suffix |\n| `autoConnect` | `true` | Fetch snapshot + open WS on mount |\n| `pollingIntervalMs` | `30000` | GraphQL poll interval while WS is not connected |\n| `enablePollingFallback` | `true` | Poll GraphQL when socket is down |\n| `cache` | `false` | Persist flags in `localStorage` |\n\n## Hooks\n\n| Hook | Returns |\n|------|---------|\n| `useFeatureFlags()` | `{ flags, flagsByName, connected, status, error, loading, refresh, config }` |\n| `useFeatureFlag(name)` | `{ flag, loading, error }` |\n| `useFeatureFlagValue(name, default?)` | flag value or default |\n| `useBooleanFlag(name, default?)` | `boolean` |\n| `useNumberFlag(name, default?)` | `number` |\n| `useStringFlag(name, default?)` | `string` |\n\n## Utilities (non-hook)\n\n```ts\nimport {\n  isFlagEnabled,\n  getFlagValue,\n  getBooleanFlag,\n  getNumberFlag,\n  getStringFlag,\n} from 'ah-ff-react'\n\nisFlagEnabled(flags, 'FF_SIGN_UP_BY_EMAIL_ENABLED')\ngetNumberFlag(flags, 'FF_MAX_ITEMS', 0)\ngetStringFlag(flags, 'FF_BANNER_TEXT', '')\n```\n\nPublic flag shape is always camelCase (`valueType`), whether data came from GraphQL or WebSocket (`value_type` is normalized).\n\n## How it works\n\n1. Optional cache hydrate from `localStorage`\n2. `POST` GraphQL `featureFlags { name valueType value }`\n3. Connect Phoenix socket → channel `flags`\n4. Apply `flags_snapshot` / `flags_updated` events\n5. If the socket is not connected and polling fallback is enabled, poll GraphQL on an interval until the socket rejoins\n\n```\nGraphQL POST  {baseUrl}/api/v1/external/feature-flags/graphql\n  Headers: X-FF-Client-Key, X-FF-Client-Env\n\nWebSocket     {wsOrigin}/api/v1/external/feature-flags/socket\n  params: { client_key, env }\n  channel: \"flags\"\n  events: flags_snapshot, flags_updated\n```\n\n## CORS / browser access\n\nThe cms-api **external feature-flags API** allows any browser origin:\n\n- HTTP: `Access-Control-Allow-Origin: *` on `/api/v1/external/feature-flags/*` (including OPTIONS preflight)\n- WebSocket: origin checks disabled for `/api/v1/external/feature-flags/socket` only\n\nAuth is still `X-FF-Client-Key` + `X-FF-Client-Env` (treat the client key like a public/publishable key). Other CMS GraphQL routes are unchanged and do not enable CORS.\n\nYou can therefore point `baseUrl` at the CMS host from any frontend:\n\n```ts\nbaseUrl: 'https://cms.assistshub.com'\n```\n\nSame-origin / proxy setups remain valid if you prefer not to call the CMS host directly:\n\n```ts\nserver: {\n  proxy: {\n    '/api': {\n      target: 'http://localhost:4000',\n      changeOrigin: true,\n      ws: true,\n    },\n  },\n}\n```\n\nThen use `baseUrl: ''` so requests go to `/api/v1/external/feature-flags/...`.\n\n## Equivalent curl\n\n```bash\ncurl -X POST 'https://cms.assistshub.com/api/v1/external/feature-flags/graphql' \\\n  -H 'Content-Type: application/json' \\\n  -H 'X-FF-Client-Key: ff-…' \\\n  -H 'X-FF-Client-Env: production' \\\n  -d '{\"query\":\"query FeatureFlags { featureFlags { name valueType value } }\",\"variables\":{}}'\n```\n\n## Scripts\n\n```bash\nnpm test\nnpm run test:coverage\nnpm run build\nnpm run typecheck\n```\n\n## Publishing (manual)\n\nThis package is prepared for a public npm release under your account. Suggested steps:\n\n1. Create GitHub repo: [https://github.com/bielorusov/ah-ff-react](https://github.com/bielorusov/ah-ff-react)\n2. Push the contents of this folder (or split from the monorepo)\n3. `npm login`\n4. `npm publish` (uses `\"publishConfig\": { \"access\": \"public\" }`)\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-b0d7fd0188a77991479ae67b44c5d868"}