{"id":19494692,"url":"https://github.com/ceejbot/reflip","last_synced_at":"2025-02-25T20:25:56.164Z","repository":{"id":11123844,"uuid":"13484124","full_name":"ceejbot/reflip","owner":"ceejbot","description":"key-value-store-backed feature flipping middleware for connect/express.js","archived":false,"fork":false,"pushed_at":"2015-03-21T00:24:12.000Z","size":391,"stargazers_count":4,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-24T03:56:40.075Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ceejbot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-10T22:05:30.000Z","updated_at":"2015-08-26T17:46:16.000Z","dependencies_parsed_at":"2022-08-28T17:13:23.980Z","dependency_job_id":null,"html_url":"https://github.com/ceejbot/reflip","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceejbot%2Freflip","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceejbot%2Freflip/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceejbot%2Freflip/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceejbot%2Freflip/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ceejbot","download_url":"https://codeload.github.com/ceejbot/reflip/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240740181,"owners_count":19849897,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T21:32:10.740Z","updated_at":"2025-02-25T20:25:56.106Z","avatar_url":"https://github.com/ceejbot.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# reflip\n\nRedis-backed or file-backed feature flipping middleware for connect/express.js. It should be straightforward to write additional storage adapters.\n\n\n[![Tests](http://img.shields.io/travis/ceejbot/reflip.svg?style=flat)](http://travis-ci.org/ceejbot/reflip) ![Coverage](http://img.shields.io/badge/coverage-93%25-green.svg?style=flat)  [![Dependencies](http://img.shields.io/david/ceejbot/reflip.svg?style=flat)](https://david-dm.org/ceejbot/reflip) ![io.js supported](https://img.shields.io/badge/io.js-supported-green.svg?style=flat)\n\n## Example\n\n```javascript\nvar Reflip = require('reflip'),\n    express = require('express');\n\nvar reflip = new Reflip(\n{\n    storage: new Reflip.RedisAdapter(\n    {\n        client: redis.createClient(),\n        namespace: 'myapp:',\n        ttl: 60 * 1000\n    }),\n});\n\nreflip.register('aardvarks', 'custom', function(request)\n{\n    return reflip.check('aardvark-enabled') \u0026\u0026 !!request.user;\n});\n\nvar app = express();\napp.use(express.session()); // etc\napp.use(reflip.flip());\n\napp.get('/aardvarks', serveArdvarks);\napp.get('/anteaters', reflip.gate('anteaters'), serveAnteaters);\n\nfunction serveArdvarks(request, response)\n{\n    // has access to the session info; can make exciting decisions\n    if (!request.check('aardvarks') || request.session.alpacasPreferred)\n    {\n        response.redirect('/alpacas');\n        return;\n    }\n\n    response.render('aardvarks');\n}\n\nfunction serveAnteaters(request, response)\n{\n    // if we reach here at all, the anteater feature is enabled\n    response.render('anteaters');\n}\n```\n\n## API\n\n### new Reflip(options)\n\nThe options object may include the following fields:\n\n- `storage`: a storage adapter; can be nil if you are operating from a predefined object only\n- `default`: default response for unknown features; defaults to `false`\n- `httpcode`: the status code to use when blocking requests for disabled features; defaults to 404\n- `features`: an object pre-defining feature defaults; is overridden after `ttl` milliseconds by the values in remote storage if that is enabled\n- `exportName`: the name of the function to hang onto each request object; use if `check()` would conflict with other Connect middleware\n\n\n### reflip.register('feature-name', function(request) {})\n\nRegister a custom feature name with a function that decides based on the request object if it is enabled or not. Can return a promise. (This is a lie; I need to make this work.)\n\n### reflip.register(new Reflip.Feature(opts))\n\nRegister a pre-constructed feature that makes its decisions in any manner you define.\n\n### reflip.flip()\n\nThe middleware that decorates each request object with the result of feature checks.\n\n```javascript\napp.use(reflip.flip());\n```\n\nThe middleware pre-calculates the answers for all feature checks \u0026 adds a `check()` function to each request object.\n\n### request.check('feature-name')\n\nDetermines if the named feature is turned on or not. Returns a falsey value if this feature is off for this request. Returns a truthy value if it should be enabled. If the feature is of type `grouped`, it will return a string indicating which group this request is in.\n\n### request.check()\n\nReturns the internal cache of all feature names and their corresponding states for this request. Note that mutating the cache will affect the results returned by `request.check('feature-name')`.\n\n### reflip.gate('feature-name', [failureHandler])\n\nUse as middleware for specific routes; responds to the request with `reflip.httpcode` if the feature is not enabled for that request.\n\n```javascript\napp.get('/anteaters', reflip.gate('anteaters'), serveAnteaters);\n```\n\nAlternatively, you can provide a `failureHandler` function that returns a custom response. The `reflip.httpcode` property will be ignored if a failure handler is specified.\n\n```javascript\napp.get('/anteaters', reflip.gate('anteaters', function(request, response)\n{\n    response.json(403,\n    {\n        code: 'ForbiddenError',\n        message: 'This feature is unavailable.'\n    });\n}), serveAnteaters);\n```\n\n### reflip.refresh()\n\nForces the adapter layer to refresh; normally called by the interval timer. Returns a promise that resolves when the lookup is complete. Updates the list of features completely, adding new ones and removing older ones. Also updates the timeout.\n\n## Feature\n\nThe feature object represents a feature that can be flipped on or off.\n\n```javascript\nvar feature = new Reflip.Feature(\n{\n    name: 'anteaters',\n    type: 'metered',\n    chance: 50,\n});\n```\n\nValid types:\n\n* `boolean`: on or off.\n* `metered`: the feature has a percent chance of being turned on\n* `grouped`: a list of possible values for the feature, each of which has an equal chance of appearing; useful for rudimentary a/b testing\n* `custom`: you provide the function used to decide if the feature is enabled. The checker function is given the express request object to use to make its decision.\n\nValid feature fields:\n\n* `name`: required, string name of feature\n* `type`: required; string type of feature\n* `enabled`: boolean, used for boolean features\n* `groups`: array, used for grouped features\n* `chance`: number from 0-100, used for metered features\n* `checker`: called by check() in lieu of other decision-making; used for custom features\n\n## File adapter\n\nUsage: `storage = new Reflip.FileAdapter({ filename: './features.json'});`\n\nExample file:\n\n```javascript\n{\n    \"features\":\n    [\n        {\n            \"name\": \"aardvarks\",\n            \"type\": \"boolean\",\n            \"enabled\": true\n        },\n        {\n            \"name\": \"archaeopteryx\",\n            \"type\": \"boolean\",\n            \"enabled\": false\n        },\n        {\n            \"name\": \"anteaters\",\n            \"type\": \"metered\",\n            \"enabled\": true,\n            \"chance\": 25\n        },\n        {\n            \"name\": \"alpacas\",\n            \"type\": \"grouped\",\n            \"enabled\": true,\n            \"groups\": [ \"a\", \"b\", \"c\" ]\n        }\n    ]\n}\n```\n\nThe `ttl` field, if present in the features hash, is ignored. The file adapter uses `fs.watch()` to observe changes in the file. The [usual fs.watch() caveats](http://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener) apply.\n\n## Redis adapter\n\n```javascript\nvar storage = new Reflip.RedisAdapter(\n{\n    client: redis.createClient(), // or supply `host` and `port` fields\n    namespace: 'key-prefix:',     // optional\n    ttl: 60000                    // optional; refresh interval in milliseconds; defaults to 5 minutes\n});\n```\n\nThe redis adapter expects to find the following keys:\n\n* `key-prefix:ttl`: integer giving time until next refresh, in milliseconds\n* `key-prefix:features`: set giving string feature names\n* `key-prefix:\u003cname\u003e`: hash, one per feature. The hash is passed directly to the Feature constructor as documented above.\n\n## TODO\n\nThe module should work well as a express middleware right now. However, it would be nice to have conveniences for adding/editing/removing feature switches from redis, out of band of the Reflip instance (since it is intended to update itself periodically). Maybe a tiny web app that does nothing but update redis?\n\n## LICENSE\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceejbot%2Freflip","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fceejbot%2Freflip","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceejbot%2Freflip/lists"}