{"id":13453247,"url":"https://github.com/franciscop/brownies","last_synced_at":"2025-05-15T06:02:24.469Z","repository":{"id":38272619,"uuid":"67251638","full_name":"franciscop/brownies","owner":"franciscop","description":"🍫 Tastier cookies, local, session, and db storage in a tiny package. Includes subscribe() events for changes.","archived":false,"fork":false,"pushed_at":"2019-05-11T02:24:23.000Z","size":2585,"stargazers_count":2380,"open_issues_count":0,"forks_count":63,"subscribers_count":43,"default_branch":"master","last_synced_at":"2025-05-12T19:57:29.443Z","etag":null,"topics":["browser","cookie","javascript","localstorage","sessionstorage","storage"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/franciscop.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-09-02T20:08:23.000Z","updated_at":"2025-04-26T04:53:02.000Z","dependencies_parsed_at":"2022-08-18T15:22:31.168Z","dependency_job_id":null,"html_url":"https://github.com/franciscop/brownies","commit_stats":null,"previous_names":["franciscop/cookies.js"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fbrownies","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fbrownies/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fbrownies/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fbrownies/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/franciscop","download_url":"https://codeload.github.com/franciscop/brownies/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253814981,"owners_count":21968560,"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":["browser","cookie","javascript","localstorage","sessionstorage","storage"],"created_at":"2024-07-31T08:00:36.453Z","updated_at":"2025-05-15T06:02:24.422Z","avatar_url":"https://github.com/franciscop.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","browser","TypeScript"],"sub_categories":[],"readme":"# Brownies [![npm install brownies](https://img.shields.io/badge/npm%20install-brownies-blue.svg)](https://www.npmjs.com/package/brownies) [![gzip size](https://img.badgesize.io/franciscop/brownies/master/brownies.min.js.svg?compression=gzip)](https://github.com/franciscop/brownies/blob/master/brownies.min.js) [![dependencies](https://img.shields.io/badge/dependencies-0-limegreen.svg)](https://github.com/franciscop/brownies/blob/master/package.json) [![support](https://img.shields.io/badge/es-6-limegreen.svg)](https://caniuse.com/#feat=proxy) [![playground](https://img.shields.io/badge/play-jsfiddle-blue.svg)](https://jsfiddle.net/oc5ju3ge/)\n\nTastier `cookies`, `local`, `session`, and `db` storage in a tiny package:\n\n```js\nimport { cookies, local, db } from 'brownies';\n\ncookies.token = 42;     // Set it\nlet t = cookies.token;  // Get it\ndelete cookies.token;   // Eat it\n\nlocal.token = 42;       // Set it\nlet t = local.token;    // Get it\ndelete local.token;     // Del it\n\n// db is ASYNC so read is different\ndb.token = 42;          // Set it\nlet t = await db.token; // Get it\ndelete db.token;        // Del it\n```\n\nSubscribe to changes in any of the objects:\n\n```js\nimport { session, subscribe } from 'brownies';\n\nsubscribe(session, 'token', value =\u003e {\n  console.log(value);   // 42, 'Hello', null\n});\n\nsession.token = 42;\nsession.token = 'Hello';\ndelete session.token;\n```\n\nYou can also [iterate them as expected](https://github.com/franciscop/brownies/blob/master/src/cookies.test.js) with `Object.keys()`, `Object.values()`, etc:\n\n```js\ncookies.token = 42;\ncookies.name = 'Francisco';\n\nconsole.log(Object.keys(cookies)); // token, name\n\nfor (let val of cookies) {\n  console.log(val); // 42, 'Francisco'\n}\n```\n\n\n\n## Getting started\n\nInstall it with npm:\n\n```\nnpm install brownies\n```\n\nThen import the different parts:\n\n```js\nimport { cookies, local, ... } from 'brownies';\nconst { cookies, local, ... } = require('brownies');\n```\n\nOr use a CDN for the browser:\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/brownies\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  // Extract it since we only define `brownies` globally\n  const { cookies, local, ... } = brownies;\n\u003c/script\u003e\n```\n\nIf you just want to play, go to the [**JSFiddle playground**](https://jsfiddle.net/oc5ju3ge/).\n\n\n\n## Cookies\n\nManipulate cookies with the simple getter/setter interface:\n\n```js\nimport { cookies } from 'brownies';\n\ncookies.token = 42;          // Set it\nconst res = cookies.token;   // Get it\ndelete cookies.token;        // Eat it\n```\n\nCookies will retain the types that is set. This is possible thanks to [the underlying library](https://github.com/franciscop/cookies):\n\n```js\ncookies.id = 1;\ncookies.accepted = true;\ncookies.name = 'Francisco';\ncookies.friends = [3, 5];\ncookies.user = { id: 1, accepted: true, name: 'Francisco' };\nconsole.log(typeof cookies.id);               // 'number'\nconsole.log(typeof cookies.accepted);         // 'boolean'\nconsole.log(typeof cookies.name);             // 'string'\nconsole.log(Array.isArray(cookies.friends));  // true\nconsole.log(typeof cookies.user);             // 'object'\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eWarning: Manually setting cookies\u003c/strong\u003e with \u003ccode\u003edocument.cookie\u003c/code\u003e or server-side [click for details]\u003c/summary\u003e\n\nValues are encoded first with `JSON.stringify()` to allow for different types, and then with `encodeURIComponent()` to remain RFC 6265 compliant. See the details in [the underlying library](https://github.com/franciscop/cookies#advanced-options). If you are setting cookies manually, you'll have to follow the same process:\n\n```js\nimport { cookies } from 'brownies';\ndocument.cookie = `name=${encodeURIComponent(JSON.stringify('Francisco'))}`\nconsole.log(cookies.name);  // Francisco\n```\n\n\u003chr /\u003e\n\u003c/details\u003e\n\nTo delete a item, you have to call `delete` on it as you would normally do with object properties:\n\n```js\nconsole.log(cookies.id);  // null\ncookies.id = 1;\nconsole.log(cookies.id);  // 1\ndelete cookies.id;\nconsole.log(cookies.id);  // null\n```\n\n\u003e Note: the default value for deleted cookies is set to `null` to be consistent with other local storage technologies.\n\nYou can iterate over the cookies in many different standard ways as normal:\n\n```js\nObject.keys(cookies);\nObject.values(cookies);\nObject.entries(cookies);\nfor (let key in cookies) {}\nfor (let val of cookies) {}\n```\n\n\n### Options\n\nYou can change the [cookies **options**](https://github.com/franciscop/cookies#options) globally:\n\n```js\nimport { cookies, options } from 'brownies';\n\n// Options with its defaults. Note that expires is set to 100 days\ncookies[options] = {\n  expires: 100 * 24 * 3600,     // The time to expire in seconds\n  domain: false,                // The domain for the cookie\n  path: '/',                    // The path for the cookie\n  secure: https ? true : false  // Require the use of https\n};\n\ncookies.token = 24;  // Will be stored for ~100 days\n```\n\n\u003e **WARNING**: you should import `options` and then use it as a variable like `cookies[options]`. You CANNOT do ~~`cookies.options`~~ nor ~~`cookies['options']`~~.\n\n\n\n\n## LocalStorage\n\nFor `localStorage`, we define `local` to simplify the interface:\n\n```js\nimport { local } from 'brownies';\n\nlocal.token = 42;          // Set it\nconst res = local.token;   // Get it\ndelete local.token;        // Remove it\n```\n\nlocalStorage items can be set to many different standard values, and they will retain the types:\n\n```js\nlocal.id = 1;\nlocal.accepted = true;\nlocal.name = 'Francisco';\nlocal.friends = [3, 5];\nlocal.user = { id: 1, accepted: true, name: 'Francisco' };\nconsole.log(typeof local.id);               // 'number'\nconsole.log(typeof local.accepted);         // 'boolean'\nconsole.log(typeof local.name);             // 'string'\nconsole.log(Array.isArray(local.friends));  // true\nconsole.log(typeof local.user);             // 'object'\n```\n\n\u003e Since 2.0 we are using custom data storage to keep the types consistent, but this means that you cannot read items that were set by `brownies` like ~~`localStorage.getItem(KEY)`~~. Please use the `local.KEY` provided by `brownies` API instead.\n\nTo delete a item, you have to call `delete` on it as you would normally do with object properties:\n\n```js\nconsole.log(local.id);  // null\nlocal.id = 1;\nconsole.log(local.id);  // 1\ndelete local.id;\nconsole.log(local.id);  // null\n```\n\nYou can iterate over the items in many different standard ways as normal:\n\n```js\nObject.keys(local);\nObject.values(local);\nObject.entries(local);\nfor (let key in local) {}\nfor (let val of local) {}\n```\n\nSo if you wanted to delete them all, you can do so by looping them easily:\n\n```js\nfor (let key in local) {\n  console.log('Deleting:', key, local[key]);\n  delete local[key];\n}\n```\n\n\n\n## SessionStorage\n\nFor the `sessionStorage`, we define `session` to simplify the interface:\n\n```js\nimport { session } from 'brownies';\n\nsession.token = 42;          // Set it\nconst res = session.token;   // Get it\ndelete session.token;        // Remove it\n```\n\nsessionStorage items can be set to many different standard values, and they will retain the types:\n\n```js\nsession.id = 1;\nsession.accepted = true;\nsession.name = 'Francisco';\nsession.friends = [3, 5];\nsession.user = { id: 1, accepted: true, name: 'Francisco' };\nconsole.log(typeof session.id);               // 'number'\nconsole.log(typeof session.accepted);         // 'boolean'\nconsole.log(typeof session.name);             // 'string'\nconsole.log(Array.isArray(session.friends));  // true\nconsole.log(typeof session.user);             // 'object'\n```\n\n\u003e Since 2.0 we are using custom data storage to keep the types consistent, but this means that you cannot read items that were set by `brownies` like ~~`localStorage.getItem(KEY)`~~. Please use the `local.KEY` provided by `brownies` API instead.\n\nTo delete a item, you have to call `delete` on it as you would normally do with object properties:\n\n```js\nconsole.log(session.id);  // null\nsession.id = 1;\nconsole.log(session.id);  // 1\ndelete session.id;\nconsole.log(session.id);  // null\n```\n\nYou can iterate over the items in many different standard ways as normal:\n\n```js\nObject.keys(session);\nObject.values(session);\nObject.entries(session);\nfor (let key in session) {}\nfor (let val of session) {}\n```\n\nSo if you wanted to delete them all, you can do so by looping them easily:\n\n```js\nfor (let key in session) {\n  console.log('Deleting:', key, session[key]);\n  delete session[key];\n}\n```\n\n\n\n## Subscribe\n\nSubscribe allows you to listen to changes to *any* object, including yours:\n\n```js\nimport { local, subscribe } from 'brownies';\n\nsubscribe(local, 'token', value =\u003e {\n  console.log(value);   // 42, null, 'Hello'\n});\n\nlocal.token = 42;\ndelete local.token;\nlocal.token = 'Hello';\n```\n\n**Warning**: `subscribe()` cannot guarantee being sync, so the above might not trigger if the end value is the same as the initial value or middle steps might not be shown.\n\nChanges work even if you use the native API to change the values, or even if the changes happen on another tab:\n\n```js\nimport { local, subscribe } from 'brownies';\n\nsubscribe(local, 'token', value =\u003e {\n  console.log(value);   // abc (string)\n});\n\n// Note that this is the native one:\nlocalStorage.setItem('token', 'abc');\n```\n\nTo unsubscribe, store the value returned by `subscribe()` and then use it with `unsubscribe()`:\n\n```js\nimport { cookies, subscribe, unsubscribe } from 'brownies';\n\nconst id = subscribe(cookies, 'token', token =\u003e {\n  console.log(token);\n});\n\nunsubscribe(id);\n```\n\nYou can also unsubscribe by the callback, which is very useful in a React context:\n\n```js\nimport { cookies, subscribe, unsubscribe } from 'brownies';\n\nconst cb = token =\u003e console.log('NEW TOKEN:', token);\nsubscribe(cookies, 'token', cb);\nunsubscribe(cb);\n```\n\nFor instance, if you want to keep the user points synced across tabs with localStorage:\n\n```js\nimport { local, subscribe, unsubscribe } from 'brownies';\n\nexport default class extends React.Component {\n  constructor (props) {\n    super(props);\n    this.state = { points: local.points };\n    this.updatePoints = this.updatePoints.bind(this);\n  }\n  updatePoints (points) {\n    this.setState({ points });\n  }\n  componentDidMount () {\n    subscribe(local, 'points', this.updatePoints);\n  }\n  componentWillUnmount () {\n    unsubscribe(this.updatePoints);\n  }\n  render () {\n    return \u003cdiv\u003ePoints: {this.state.points}\u003c/div\u003e;\n  }\n}\n```\n\n**Warning**: try to keep the number of subscriptions low since each will incur in a performance cost.\n\n\n\n### Trivia\n\nMy former coworker made delicious brownies when leaving the company and asked me to name a library brownies. I thought it was a fantastic idea, since [brownies are tastier cookies](https://wow-cookies.com/brownies-are-they-cookies-or-cake/) after all 🙂.\n\nThis library was previously named `clean-store`, but I never really liked that name. The stars in this repository [were transferred from the previous repository](https://francisco.io/blog/transferring-github-stars/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranciscop%2Fbrownies","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffranciscop%2Fbrownies","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranciscop%2Fbrownies/lists"}