{"_id":"bullet","_rev":"13-3185a290659b19e24fcfa819fce81e9b","name":"bullet","dist-tags":{"latest":"0.0.7"},"versions":{"0.0.1":{"name":"bullet","version":"0.0.1","author":{"name":"James Kerr"},"license":"MIT","_id":"bullet@0.0.1","maintainers":[{"name":"jkerr","email":"kerr@hey.com"}],"dist":{"shasum":"3506474e495752c5304074c5ba7c6d619906f135","tarball":"https://registry.npmjs.org/bullet/-/bullet-0.0.1.tgz","fileCount":4,"integrity":"sha512-tDLM7AHMbWa9Ig8BVSwhqSsdZFVhwsMJW+2LbhilCOEBtELl1jjEYaXQ2jrtEdvqFsdIHurJwH01/LWxnaHTPA==","signatures":[{"sig":"MEYCIQDvDnQrIA2oNwqKpJU6MWE+2HZXa6OhcFD9cbnVyhBj+gIhAOz1WY3rCHFU5A85kBxhQOu11qtM9xOg2ZVveGqO+ax8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":184},"main":"index.js","_npmUser":{"name":"jkerr","email":"kerr@hey.com"},"description":"Build Apps Fast","directories":{},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/bullet_0.0.1_1714081726892_0.10657368533937017","host":"s3://npm-registry-packages"}},"0.0.2":{"name":"bullet","version":"0.0.2","license":"MIT","_id":"bullet@0.0.2","maintainers":[{"name":"jkerr","email":"kerr@hey.com"}],"dist":{"shasum":"8e12d3b42a914d1e1aa240067753a0e9198cf9ad","tarball":"https://registry.npmjs.org/bullet/-/bullet-0.0.2.tgz","fileCount":17,"integrity":"sha512-INQSluXDI/fHfB8qDd87ZmkSG/s+GCZsoCZ7QeS+6f4WCcwGEZxhpIKgW/nXH+/Cs0OLeg9VuL58r7Ioe8DfbA==","signatures":[{"sig":"MEUCIQDwgRpRmpKzJ+nELV9MNgo6pEyHFb7JGRiZqrGsYV0EXQIgXIf2Oc6j/7Bbvf5uMY+bvsQdpPL4orX/+PDSHOc+KG0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":22523},"type":"module","types":"./bullet.d.ts","exports":"./bullet.js","gitHead":"06ea9ccbb2c711e3f1033a00961a9fa13c96165d","scripts":{"test":"node --test","types":"tsc bullet.js --allowJs -d --outdir . --skipLibCheck --module esnext --declaration --emitDeclarationOnly","test-watch":"node --test --watch"},"_npmUser":{"name":"jkerr","email":"kerr@hey.com"},"directories":{},"dependencies":{"lodash-es":"^4.17.21","pluralize":"^8.0.0","@reduxjs/toolkit":"^2.2.5"},"_hasShrinkwrap":false,"packageManager":"yarn@4.3.0","devDependencies":{"typescript":"^5.5.2"},"_npmOperationalInternal":{"tmp":"tmp/bullet_0.0.2_1721951345582_0.9305462741678066","host":"s3://npm-registry-packages"}},"0.0.3":{"name":"bullet","version":"0.0.3","license":"MIT","_id":"bullet@0.0.3","maintainers":[{"name":"jkerr","email":"kerr@hey.com"}],"dist":{"shasum":"91785974b6e79e81bf33713a1d3e841ca8872b68","tarball":"https://registry.npmjs.org/bullet/-/bullet-0.0.3.tgz","fileCount":19,"integrity":"sha512-2lq3ZTyR6cmePJlVfzyhj64mJCIVEsVNEI/oqC3AScbwu6jHwjGLSOsJAx0pvCzwUq4IuV7IOppgENFHia1AZA==","signatures":[{"sig":"MEUCIHlK8yprjmtaXdgyOcWOVC3E+zYfiW32Sz4M9pCdeGePAiEAvSAHGDZ45vieyhDQCiZNwzLKDwt7oIowenuGFas1cuc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":27732},"type":"module","types":"./bullet.d.ts","exports":"./bullet.js","gitHead":"8ed77e33b7abd66a86bcbbf17650268f3f7765c0","scripts":{"test":"node --test","types":"tsc bullet.js --allowJs -d --outdir . --skipLibCheck --module esnext --declaration --emitDeclarationOnly","test-watch":"node --test --watch"},"_npmUser":{"name":"jkerr","email":"kerr@hey.com"},"_npmVersion":"10.2.4","description":"If you've been using Redux & React for any amount of time, you may find yourself writing the same types of reducers over and over again. In my experience, there are **only two types of reducers** that cover 90% of my state needs.","directories":{},"_nodeVersion":"21.6.2","dependencies":{"lodash-es":"^4.17.21","pluralize":"^8.0.0","@reduxjs/toolkit":"^2.2.5"},"_hasShrinkwrap":false,"packageManager":"yarn@4.3.0","devDependencies":{"typescript":"^5.5.2"},"_npmOperationalInternal":{"tmp":"tmp/bullet_0.0.3_1722626656804_0.4068388795964868","host":"s3://npm-registry-packages"}},"0.0.4":{"name":"bullet","version":"0.0.4","license":"MIT","_id":"bullet@0.0.4","maintainers":[{"name":"jkerr","email":"kerr@hey.com"}],"dist":{"shasum":"8fd3db2c2c66de326b94a56a9d127d61a5f8c6a3","tarball":"https://registry.npmjs.org/bullet/-/bullet-0.0.4.tgz","fileCount":19,"integrity":"sha512-2cRjKgFxtonVsqErpwPjaap0qKSa6jrOQkxF+pCIkpUwHnP8t2QFEGn4BJvZJPHW5dW9YOs33oo5gL2w1ii1Lg==","signatures":[{"sig":"MEQCIC2iTGodWMLTyJXfcoD8gQv43mXq6qkhilsmvo1k9I4VAiBK83HzbiIHHYw8Y+GNLsXpz1LfPvr5sbtSJPsj+iYAhw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":28000},"type":"module","types":"./bullet.d.ts","exports":"./bullet.js","gitHead":"b26a7f989601c137768f383273529db926acc004","scripts":{"test":"node --test","types":"tsc bullet.js --allowJs -d --outdir . --skipLibCheck --module esnext --declaration --emitDeclarationOnly","test-watch":"node --test --watch"},"_npmUser":{"name":"jkerr","email":"kerr@hey.com"},"_npmVersion":"10.2.4","description":"If you've been using Redux & React for any amount of time, you may find yourself writing the same types of reducers over and over again. In my experience, there are **only two types of reducers** that cover 90% of my state needs.","directories":{},"_nodeVersion":"21.6.2","dependencies":{"lodash-es":"^4.17.21","pluralize":"^8.0.0","@reduxjs/toolkit":"^2.2.5"},"_hasShrinkwrap":false,"packageManager":"yarn@4.3.0","devDependencies":{"typescript":"^5.5.2"},"_npmOperationalInternal":{"tmp":"tmp/bullet_0.0.4_1726250564492_0.21108339398582454","host":"s3://npm-registry-packages"}},"0.0.5":{"name":"bullet","version":"0.0.5","license":"MIT","_id":"bullet@0.0.5","maintainers":[{"name":"jkerr","email":"kerr@hey.com"}],"dist":{"shasum":"c023bb5da0a90951f188aa203c2578dc42e16d93","tarball":"https://registry.npmjs.org/bullet/-/bullet-0.0.5.tgz","fileCount":20,"integrity":"sha512-o5IEUiOZYnbMja0linxtthWgEYul65iYX+mi70+USegLdn41msK3NWCULkvEi230CZZ2eA967gtgsfemqQoF5w==","signatures":[{"sig":"MEUCIFlTHqCRlPC4q05a0sj8YFf2fz7bTD5SH5wnchm6mhH6AiEA0DTXQMry5nKsTNQ07OMa7dDxo6bMFfUN96omyeTrY+Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":28334},"type":"module","types":"./bullet.d.ts","exports":"./bullet.js","gitHead":"c3092256d4316c234d80e295c29132915b803a21","scripts":{"test":"node --test","test-watch":"node --test --watch"},"_npmUser":{"name":"jkerr","email":"kerr@hey.com"},"_npmVersion":"10.2.4","description":"If you've been using Redux & React for any amount of time, you may find yourself writing the same types of reducers over and over again. In my experience, there are **only two types of reducers** that cover 90% of my state needs.","directories":{},"_nodeVersion":"21.6.2","dependencies":{"lodash-es":"^4.17.21","pluralize":"^8.0.0","@reduxjs/toolkit":"^2.2.5"},"_hasShrinkwrap":false,"packageManager":"yarn@4.3.0","devDependencies":{"typescript":"^5.5.2"},"_npmOperationalInternal":{"tmp":"tmp/bullet_0.0.5_1726250977144_0.21019645424861766","host":"s3://npm-registry-packages"}},"0.0.6":{"name":"bullet","version":"0.0.6","license":"MIT","_id":"bullet@0.0.6","maintainers":[{"name":"jkerr","email":"kerr@hey.com"}],"dist":{"shasum":"754b9e04f63b286bd431bc000318a9c08c5e8592","tarball":"https://registry.npmjs.org/bullet/-/bullet-0.0.6.tgz","fileCount":20,"integrity":"sha512-3Pr4/n4HEBYW+MXMS7ief5kof8SNMvK1Bl+TZl0n4CUpG0fNXJEBz9eAAAAXSM4uykoGcGNBtFGWUMJlCtG3Yw==","signatures":[{"sig":"MEUCIGnjYHEw0vBdMrTVRMuvJdarNzd0FYM/tk//mciYqpsIAiEA6RdRAklLx3icby9ukoaq4U14XOPA+w0mxug5sD7bHD0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":28356},"type":"module","types":"./bullet.d.ts","exports":"./bullet.js","gitHead":"1212df2856252a9d49e6c478691519f1c0ddd815","scripts":{"test":"node --test","test-watch":"node --test --watch"},"_npmUser":{"name":"jkerr","email":"kerr@hey.com"},"_npmVersion":"10.2.4","description":"If you've been using Redux & React for any amount of time, you may find yourself writing the same types of reducers over and over again. In my experience, there are **only two types of reducers** that cover 90% of my state needs.","directories":{},"_nodeVersion":"21.6.2","dependencies":{"lodash-es":"^4.17.21","pluralize":"^8.0.0","@reduxjs/toolkit":"^2.2.5"},"_hasShrinkwrap":false,"packageManager":"yarn@4.3.0","devDependencies":{"typescript":"^5.5.2"},"_npmOperationalInternal":{"tmp":"tmp/bullet_0.0.6_1726252598707_0.557254689455855","host":"s3://npm-registry-packages"}},"0.0.7":{"name":"bullet","version":"0.0.7","exports":"./bullet.js","types":"./bullet.d.ts","license":"MIT","type":"module","packageManager":"yarn@4.3.0","dependencies":{"@reduxjs/toolkit":"^2.2.5","lodash-es":"^4.17.21","pluralize":"^8.0.0"},"devDependencies":{"typescript":"^5.5.2"},"scripts":{"test-watch":"node --test --watch","test":"node --test"},"_id":"bullet@0.0.7","gitHead":"8bc257373512d9dc30bbb53d2ffd8b98ba32995c","description":"If you've been using Redux & React for any amount of time, you may find yourself writing the same types of reducers over and over again. In my experience, there are **only two types of reducers** that cover 90% of my state needs.","_nodeVersion":"21.6.2","_npmVersion":"10.2.4","dist":{"integrity":"sha512-VsPRHnMbIBGtRF2ZB1x2GFtrWEn+4x+oMuyd42m33QkxGAOuQYA5DWJRgybeLVWA3fSP/eGoL67F3ef7lKmoxQ==","shasum":"68dff982cca9be07c14295324ef69eb147c18804","tarball":"https://registry.npmjs.org/bullet/-/bullet-0.0.7.tgz","fileCount":21,"unpackedSize":30261,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEA4NhWK4mK4imK4txc/alchefW8OUNv1VkoLz45KcxjAiB71RqkKcTJD/DVJOy/nRsasoeOchCqVD3E6DSfe5USBQ=="}]},"_npmUser":{"name":"jkerr","email":"kerr@hey.com"},"directories":{},"maintainers":[{"name":"jkerr","email":"kerr@hey.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/bullet_0.0.7_1727392660294_0.8276088382670248"},"_hasShrinkwrap":false}},"time":{"created":"2024-04-25T21:48:46.891Z","modified":"2024-09-26T23:17:40.679Z","0.1.0":"2013-02-06T19:28:46.724Z","0.0.1":"2024-04-25T21:48:47.048Z","0.0.2":"2024-07-25T23:49:05.734Z","0.0.3":"2024-08-02T19:24:17.052Z","0.0.4":"2024-09-13T18:02:44.670Z","0.0.5":"2024-09-13T18:09:37.323Z","0.0.6":"2024-09-13T18:36:38.924Z","0.0.7":"2024-09-26T23:17:40.499Z"},"license":"MIT","description":"If you've been using Redux & React for any amount of time, you may find yourself writing the same types of reducers over and over again. In my experience, there are **only two types of reducers** that cover 90% of my state needs.","maintainers":[{"name":"jkerr","email":"kerr@hey.com"}],"readme":"# Pierce the Boilerplate\n\nIf you've been using Redux & React for any amount of time, you may find yourself writing the same types of reducers over and over again. In my experience, there are **only two types of reducers** that cover 90% of my state needs.\n\n1. The list of things.\n2. The key value object.\n\nBullet provides a high-level API for creating and interfacing with these two types of state structures. The two main exports are the classes: `Entity` and `KeyVal`\n\n## Install\n\n```bash\nyarn add bullet\n```\n\nBullet will pull in `@reduxjs/toolkit`.\n\n## Get Started\n\nLet's make an entity. First create a class that extends the `Entity` base class that ships with Bullet. Then define the attributes like the example below. An entity will include the attributes `id`, `createdAt`, and `updatedAt` automatically by default.\n\n```js\nimport { Entity } from \"bullet\";\n\nexport class Book extends Entity {\n  static attributes = {\n    title: { type: String, default: \"Untitled\" },\n    pageCount: { type: Number, default: 0 },\n    author: { type: String, default: \"Unknown\" },\n  };\n}\n```\n\nSimply defining this class will create all the redux necessities for a \"list of things\" slice of state. The actual state shape will look like `{ids: [], entities: {}}` popularized by the Entity Adapter in redux-toolkit.\n\nWhen you configure your store, you can use the `.slice` property on the `Book` class. This contains an object that looks like this.\n\n```js\nBooks.slice; // => {books: Book.reducer}\n```\n\nBy default, the name of the slice is the camelCased, plural class name, but it can be configured using the static `sliceName` attribute.\n\n```js\nclass Book extends Entity {\n  static sliceName = \"myBooks\"; /* overrides the default \"books\" */\n}\n```\n\nNow add your entity slice to the root reducer.\n\n```js\nconst store = configureStore({\n  reducer: combineReducers({\n    ...Book.slice,\n  }),\n});\n```\n\nFinally, give the Entity base class a reference to your redux store right after it's created.\n\n```js\nimport { Entity } from \"bullet\";\n\nconst store = configureStore(/* ... */);\n\nEntity.store = store;\n```\n\nI don't know about you, but I got sick of needing to import \"dispatch\" in every single one of my files. By providing the store to all your entities, you can use a terse, pleasant, high-level API to interact with your state, as you'll see in the next section.\n\n## Entity API\n\nThe API mirrors ActiveRecord, the ORM that comes with Ruby on Rails. Here are a few examples.\n\n**Create a New Book**\n\n```js\nconst book = Book.create({ title: \"The Secret Art of React\" });\n```\n\nYou can pass a partial object of the entity's attributes to `Book.create()`. It will save it to the store, and return an instance of the Book class. Getters and setters for all attributes are automatically defined based on the static `attributes` object.\n\n**Get and Set Attributes**\n\n```js\nbook.title; // \"The Secret Art of React\"\nbook.pageCount; // 0\nbook.attributes; // {id: \"1\", title: \"The Secret Art of React\", pageCount: 0, ...}\n```\n\n**Update a Book**\n\nYou can call `.save()` after mutating an instance.\n\n```js\nconst book = Book.create({ title: \"Pierce the Boilerplate\" });\nbook.pageCount = 4;\nbook.save(); // Updates the store with the new page count.\n```\n\nOr you can call `.update(attrs)` and pass a partial object of attributes to update.\n\n```js\nbook.update({ author: \"JK\" }); // Also updates the store.\n```\n\n**Initialize a Book**\n\nIf you want to instantiate a new book instance without saving it to the store, you can simply `new` one up.\n\n```js\nconst book = new Book({ title: \"React for Dummies\" }); // Not saved\n\nbook.save(); // But now it's saved and given an id.\n```\n\n**Destroy a Book**\n\nCall `.destroy()`\n\n```js\nconst book = Book.create({ title: \"Incriminating Evidence\" });\nbook.destroy(); // Removed from the store\n```\n\n**Query for Books**\n\nIf you need to get a single book by id, use `.find(id)`. It finds the attribute data in the state slice, then instantiates a new `Book` instance with those attributes.\n\n```js\nconst book = Book.find(\"1\");\n```\n\nIf you want all the books, use `.all`\n\n```js\nconst books = Book.all;\n```\n\nIf you want to filter the list of books use `.where(filters)`.\n\n```js\nconst books = Book.where({ pageCount: 99 });\n```\n\nThe `where` method will only match using strict equality on the attributes' values. This will improve as needs arise.\n\n## React Component Usage\n\nIf you wish to use these entities in React and have the component re-render when the state changes, you can \"use\" these hooks.\n\n```jsx\nfunction LibraryInventory() {\n  const everything = Book.useAll;\n  const filtered = Book.useWhere({ topic: \"Teen Vampire\" });\n  const single = Book.use(\"1\");\n}\n```\n\nThese `use` hooks utilize `useSelector` from `react-redux`. Instead of Bullet directly depending on it, it will look for that function on the Entity class. So for now you must assign the method to the Entity like you did with the store.\n\n```js\nimport { useSelector } from \"react-redux\";\nimport { Entity } from \"bullet\";\n\n// To use the selector hooks...\nEntity.useSelector = useSelector;\n```\n\n## Extending your Entity\n\nYou'll likely want to add domain specific methods to your entities. Simply define them on your class. Your entity will inherit helpful methods from the base class like `dispatch(action)` and `select(selector)` that can be used like so.\n\n```js\nclass Book extends Entities {\n  static attributes = {\n    authorId: { type: String, default: null },\n  };\n\n  burn() {\n    // Select some state and dispatch an action.\n    const canBurn = this.select(getCanBurn);\n    if (canBurn) this.dispatch(burnBook(this.id));\n  }\n\n  get author() {\n    // Utilize your other entities.\n    return Author.find(this.authorId);\n  }\n}\n```\n\n## The KeyVal Reducer\n\nDocs coming at some point.\n\n## Enjoy\n\nYou may find that creating these domain models was the missing piece of your react/redux application. In our experience, development velocity has dramatically increased now that reducers, actions, and selectors are all automatically created in the same manner and accessed with a readable, high-level API. Hopefully, you can stop importing useDispatch, drop thunks, and get back to solving your business problems.\n\n**Disclaimer**\n\nThis library is brand new. It is being put to use in the Zui application from Brim Data, but there will be feature gaps. The API will change and stabilize over time, so set expectations accordingly.\n\n**Contact**\n\nhttps://mastodon.social/@jameskerr\n\nhttps://x.com/specialCaseDev\n","readmeFilename":"README.md"}