{"_id":"@astrapi69/feature-strategy","_rev":"3-c0acf54e8039048f0b8ca567eb65ebcb","name":"@astrapi69/feature-strategy","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@astrapi69/feature-strategy","version":"0.1.0","keywords":["feature-flags","feature-gating","authorization","strategy","typescript"],"author":{"name":"Asterios Raptis"},"license":"MIT","_id":"@astrapi69/feature-strategy@0.1.0","maintainers":[{"name":"astrapi69","email":"asterios.raptis@gmx.net"}],"homepage":"https://github.com/astrapi69/feature-strategy#readme","bugs":{"url":"https://github.com/astrapi69/feature-strategy/issues"},"dist":{"shasum":"2d097c1b0b7082a09c9b159252a91799d9290446","tarball":"https://registry.npmjs.org/@astrapi69/feature-strategy/-/feature-strategy-0.1.0.tgz","fileCount":9,"integrity":"sha512-C0J74pIshwq6yjwTjnsRI/Tt6PSGUZZJRj+ZcYummhSgfkdQ2fLPp8StjlGvTIdhHN9Dr3Y/1IFgvYThpT3U4w==","signatures":[{"sig":"MEQCICz9K6Vazr2H00Cdg+HQYeDbybYESsmQGfT1CcntTCLWAiBFl07OgN+GoUeLVoEOxpt7I5mal3/bYGRXQhiH2WifPQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":94274},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"8aa067cf421a1fa0f659d694c7b9058b348c77a2","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"astrapi69","email":"asterios.raptis@gmx.net"},"repository":{"url":"git+https://github.com/astrapi69/feature-strategy.git","type":"git","directory":"packages/core"},"_npmVersion":"11.16.0","description":"Framework-agnostic feature gating and component authorization strategies with three states: active, disabled, hidden","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/feature-strategy_0.1.0_1781176723249_0.553750188392355","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@astrapi69/feature-strategy","version":"0.1.1","keywords":["feature-flags","feature-gating","authorization","strategy","typescript"],"author":{"name":"Asterios Raptis"},"license":"MIT","_id":"@astrapi69/feature-strategy@0.1.1","maintainers":[{"name":"astrapi69","email":"asterios.raptis@gmx.net"}],"homepage":"https://github.com/astrapi69/feature-strategy#readme","bugs":{"url":"https://github.com/astrapi69/feature-strategy/issues"},"dist":{"shasum":"c35b303f527a6629afd354953857ff608b2e4b16","tarball":"https://registry.npmjs.org/@astrapi69/feature-strategy/-/feature-strategy-0.1.1.tgz","fileCount":9,"integrity":"sha512-sILM3wIuOvm91Y0M5cJQymqds0i1ECY2bs13scRNEUF9qUCTJ7EmYg7IjKwWzoCnE/hIaUpWvvkD53tJnEKEEA==","signatures":[{"sig":"MEQCIFl4pJTnQyoIaY2HH7rVDLw0WKmRHf8ZaDMTw1irvGpcAiBgd+3zit7XmwYJLdjuZRQ/WQPw99Pu3Y7E0LpX1EqhFg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":95848},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"e6f48d41c662fa0dcbbd5dcc353ae3203f6a6630","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"astrapi69","email":"asterios.raptis@gmx.net"},"repository":{"url":"git+https://github.com/astrapi69/feature-strategy.git","type":"git","directory":"packages/core"},"_npmVersion":"11.16.0","description":"Framework-agnostic feature gating and component authorization strategies with three states: active, disabled, hidden","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/feature-strategy_0.1.1_1781186125055_0.6496085636983542","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@astrapi69/feature-strategy","version":"0.1.2","description":"Framework-agnostic feature gating and component authorization strategies with three states: active, disabled, hidden","keywords":["feature-flags","feature-gating","authorization","strategy","typescript"],"license":"MIT","author":{"name":"Asterios Raptis"},"repository":{"type":"git","url":"git+https://github.com/astrapi69/feature-strategy.git","directory":"packages/core"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"sideEffects":false,"scripts":{"build":"tsup","test":"vitest run","typecheck":"tsc --noEmit"},"publishConfig":{"access":"public"},"gitHead":"2cbcf78166b8f00fdb88c94c0c35ab881b1826c3","_id":"@astrapi69/feature-strategy@0.1.2","bugs":{"url":"https://github.com/astrapi69/feature-strategy/issues"},"homepage":"https://github.com/astrapi69/feature-strategy#readme","_nodeVersion":"24.15.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-o+3yHmrOMJhINNjDJMoWiqbeqUPqqioKHNz+pNbCSxXk1LPfpD+Mwc6Jx9posOsX0ByFqneORTw/FXmOhPSlIw==","shasum":"82a681592bbd192b68104fc4a6a1cb923148ce2c","tarball":"https://registry.npmjs.org/@astrapi69/feature-strategy/-/feature-strategy-0.1.2.tgz","fileCount":9,"unpackedSize":96170,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDu1EUrOqEZ1BUMMLkfH/e2dybtdqmZuIci5s+B+Q3qQgIgHkvvNrSthZBK4xCBCrOo5Fz+tcrI5QXt4UzELMKbaMk="}]},"_npmUser":{"name":"astrapi69","email":"asterios.raptis@gmx.net"},"directories":{},"maintainers":[{"name":"astrapi69","email":"asterios.raptis@gmx.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/feature-strategy_0.1.2_1781258170656_0.9822294386466015"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-11T11:18:43.015Z","modified":"2026-06-12T09:56:10.939Z","0.1.0":"2026-06-11T11:18:43.383Z","0.1.1":"2026-06-11T13:55:25.198Z","0.1.2":"2026-06-12T09:56:10.845Z"},"bugs":{"url":"https://github.com/astrapi69/feature-strategy/issues"},"author":{"name":"Asterios Raptis"},"license":"MIT","homepage":"https://github.com/astrapi69/feature-strategy#readme","keywords":["feature-flags","feature-gating","authorization","strategy","typescript"],"repository":{"type":"git","url":"git+https://github.com/astrapi69/feature-strategy.git","directory":"packages/core"},"description":"Framework-agnostic feature gating and component authorization strategies with three states: active, disabled, hidden","maintainers":[{"name":"astrapi69","email":"asterios.raptis@gmx.net"}],"readme":"# @astrapi69/feature-strategy\n\nFramework-agnostic feature gating with three states: `active`, `disabled`, `hidden`.\nZero runtime dependencies. ESM and CJS.\n\n## Install\n\n```bash\nnpm install @astrapi69/feature-strategy\n```\n\n## Usage\n\n```ts\nimport {\n    FeatureRegistry,\n    CompositeStrategy,\n    StaticFeatureStrategy,\n    RoleBasedFeatureStrategy\n} from '@astrapi69/feature-strategy';\n\ninterface AppContext {\n    user?: { roles: readonly string[] };\n}\n\nconst registry = new FeatureRegistry<AppContext>();\n\nregistry.registerAll([\n    { id: 'export', defaultState: 'active' },\n    { id: 'admin-panel', defaultState: 'hidden' },\n    { id: 'tts', defaultState: 'active' }\n]);\n\nregistry.setStrategy(\n    new CompositeStrategy<AppContext>([\n        new StaticFeatureStrategy({ tts: 'hidden' }, { tts: 'Requires a TTS engine' }),\n        new RoleBasedFeatureStrategy<AppContext>(\n            { 'admin-panel': { roles: ['admin'], reason: 'Administrators only' } },\n            (context) => context?.user?.roles ?? []\n        )\n    ])\n);\n\nregistry.getState('admin-panel', { user: { roles: ['admin'] } });\nregistry.getState('tts');\nregistry.getReason('tts');\n```\n\n## The design principle: defaults plus deviations\n\nThis is the part worth internalizing before writing your first strategy.\nDescriptors carry the normal state of a feature. Strategies contain ONLY\nthe deviation rules and abstain from everything else by returning\n`undefined`. The registry resolves in this order:\n\n1. the strategy verdict, when it returns one\n2. the descriptor `defaultState`, when the strategy abstains\n3. `hidden` for unknown feature ids (fail closed)\n\nDo not write a strategy as a total function that returns a verdict for\nevery feature. If most of your features are always active, they should\nappear only as descriptors with `defaultState: 'active'` and never in a\nstrategy. A strategy with a catch-all `return 'active'` branch duplicates\nyour feature list, turns the descriptor defaults into dead code, and\nsilently disables the fail-closed behavior for unknown ids.\n\nWhen several strategies are combined through `CompositeStrategy`, the most\nrestrictive non-abstaining verdict wins: `hidden` beats `disabled` beats\n`active`. Abstention is what makes this composition work, since each\nstrategy only speaks up about the features it actually governs. Note that\nper-feature outcomes configured inside a single strategy, such as the\n`missingState` of a role requirement, are that strategy's verdict only:\ncomposition can escalate the result to a more restrictive state when\nanother strategy demands it, but it can never soften it.\n\n## Conditions must be cheap and pure\n\nEvaluation is lazy and happens on demand: every `getState` call evaluates\nthe strategy, and consumers such as the React adapter call it on render.\n`CompositeStrategy.getReason` additionally re-evaluates `getState` per\ninner strategy to find the winning verdict.\n\nFor static maps this is a lookup and costs nothing. For\n`ConditionalFeatureStrategy`, write conditions as synchronous, pure\nlookups on the context object: no async work, no DOM access, no storage\nreads, no computation. Anything dynamic belongs in the context, which the\napplication builds once and passes in, not in the condition.\n\n## Strategies\n\n`StaticFeatureStrategy` maps feature ids to fixed states.\n`ConditionalFeatureStrategy` evaluates rules against a context.\n`RoleBasedFeatureStrategy` resolves states from role requirements through\nan application supplied role extractor, without imposing a user model.\n`CompositeStrategy` combines any of the above.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}