{"id":23440774,"url":"https://github.com/dabapps/redux-api-collections","last_synced_at":"2025-10-12T17:38:09.079Z","repository":{"id":34361636,"uuid":"107700111","full_name":"dabapps/redux-api-collections","owner":"dabapps","description":null,"archived":false,"fork":false,"pushed_at":"2023-01-05T16:16:25.000Z","size":931,"stargazers_count":0,"open_issues_count":14,"forks_count":0,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-09-30T13:43:57.242Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dabapps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-20T16:18:26.000Z","updated_at":"2020-03-26T16:40:08.000Z","dependencies_parsed_at":"2023-01-15T06:34:20.328Z","dependency_job_id":null,"html_url":"https://github.com/dabapps/redux-api-collections","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/dabapps/redux-api-collections","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabapps%2Fredux-api-collections","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabapps%2Fredux-api-collections/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabapps%2Fredux-api-collections/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabapps%2Fredux-api-collections/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dabapps","download_url":"https://codeload.github.com/dabapps/redux-api-collections/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabapps%2Fredux-api-collections/sbom","scorecard":{"id":316250,"data":{"date":"2025-08-11","repo":{"name":"github.com/dabapps/redux-api-collections","commit":"5ccb24311bc64698bb1adbec6c8c68d228a5f07a"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Code-Review","score":10,"reason":"all changesets reviewed","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"43 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-4w2v-q235-vp99","Warn: Project is vulnerable to: GHSA-cph5-m8f7-6c5x","Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-74fj-2j2h-c42q","Warn: Project is vulnerable to: GHSA-pw2r-vq6v-hr8c","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-x5rq-j2xg-h7qm","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-cf4h-3jhx-xvhq","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T00:14:28.541Z","repository_id":34361636,"created_at":"2025-08-18T00:14:28.541Z","updated_at":"2025-08-18T00:14:28.541Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279012186,"owners_count":26085079,"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","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-12-23T16:18:35.969Z","updated_at":"2025-10-12T17:38:09.057Z","avatar_url":"https://github.com/dabapps.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redux API Collections\n[![Build Status](https://travis-ci.com/dabapps/redux-api-collections.svg?token=Vjwq9pDHXxGNhnyuktQ5\u0026branch=master)](https://travis-ci.com/dabapps/redux-api-collections)\n\nStandardised, type-safe ways of interacting with DRF-style APIs.\n\nRedux API Collections aims to simplify and standardise the way in which we access Django Rest Framework endpoints when writing Redux applications in Typescript. It offers reducers for both collection endpoints and individual item endpoints, as well as the necessary tools to shunt data into them.\n\n## Installation\nInstall via NPM:\n\n```\nnpm install @dabapps/redux-api-collections --save\n```\n\n## Getting Started\nThis project requires use of Redux-Thunk to correctly dispatch actions.  Collection endpoints must follow Django Rest Framework's pagination logic.  Item endpoints should return a single item.\n\nTo begin with, you will need to provide two interfaces that map given paths to a type or interface that represents a well-formed instance of an item being returned from the server.  You will need one collection for collection paths, and one for item paths. If you are lucky, these may be the same interface.  These paths are expected to match with your server's `/api/[endpoint]/` url and `/api/[endpoint]/[id]/` respectively.\n\n```typescript\ntype User = Readonly\u003c{\n  id: string,\n  name: string\n}\u003e;\n\ninterface Collections {\n  users: User  // This is /api/users/ on the server\n}\n\ninterface Items {\n  users: User  // This is /api/users/[id]/ on the server\n}\n```\n\nWith these two (or one) interfaces defined, you will need to attach them to your store. This is as simple as including the following code:\n\n```typescript\ninterface Store {\n  collections: CollectionStore\u003cCollections\u003e,\n  items: ItemStore\u003cItems\u003e\n}\n```\nYou can, in theory, mount the stores elsewhere, but this is not recommended.\n\nThe next step is providing a mapping between paths and functions that will produce instances of our objects from the server. Typically, you'll want to use Immutable's Record type or our own SimpleRecord, but if you trust the server to always give back fully realised objects, the identity function will suffice.\n\n```typescript\nconst collectionToRecordMapping = {\n  users: (user: User) =\u003e user\n}\n\nconst itemToRecordMapping = {\n  users: (user: User) =\u003e user\n}\n```\n\nNow we have mappings, plus interfaces, we can bring it all together.  You are likely to want to also mount the Responses reducer, as this gives visibility to which endpoints are currently in use.\n\n```typescript\nimport { Collections, responsesReducer } from '@dabapps/redux-api-collections';\n\nconst collections = Collections\u003cCollections, Items\u003e(collectionToRecordMapping, itemToRecordMapping);\n\n// elsewhere\ncombineReducers({\n  collections: collections.reducers.collectionsReducer,\n  items: collections.reducers.itemsReducer,\n\n  responses: responsesReducer,\n});\n```\n\nThe Collections object gives you access to type-safe helper functions for requesting data.\n\nFor example, we want to get a specific collection endpoint. We can call `collections.actions.getCollection` to request data from the server.\n\n```typescript\nconst action = collections.actions.getCollection('users', options);\ndispatch(action);\n\n// Later\n\ngetCollectionByName(store.collections, 'users');\ngetCollectionResultsByName(store.collections, 'users');\n```\n\nMany functions can also be namespaced by a `subgroup` field - this will allow you to request the same endpoint from multiple places without overwriting their results.\n\n```typescript\nconst action = collections.actions.getCollection('users', options, 'loginPage');\n```\n\n## Subpaths\n\nThere are often situations where you want to have collections or items at a path with some form of ID in the middle of it. Provide your paths as something [path-to-regexp](https://github.com/pillarjs/path-to-regexp) understands:\n\n```typescript\ninterface Collections {\n  'users/:userId/other-users': User  // This is /api/users/:userId/other_users/ on the server\n}\n\ninterface Items {\n  'users/:userId/other-users': User  // This is /api/users/:userId/other_users/[id]/ on the server\n}\n```\n\nWith these two paths, we need to be able to specify a separate `userId` for it to make sense. We can construct helper functions for working with this:\n\n```typescript\nconst collectionSubpath = collections.collectionAtSubpath('users/:userId/other-users', {userId: '12345'});\nconst itemSubpath = collections.itemAtSubpath('users/:userId/other-users', {userId: '23456'});\n```\n\nYou pass a dictionary for each of the optional positions in the path.\n\nThe two resulting structures contain pre-parameterized sets of action creators, as well as accessor functions.\n\n```typescript\ncollectionSubpath.actions.getCollection(options);  // Contains a pre-bound action for each of the normal collection actions\ncollectionSubpath.getSubpathCollection(store);  // Our collection object\ncollectionSubpath.getSubpathCollectionResults(store);  // Just the items\n\nitemSubpath.actions.getItem('12345');\nitemSubpath.getSubpathItem(store);\n```\n\n## I'm stuck!\n\n### Help, my API isn't mounted at /api/\n\nYou can parameterize the Collections object with different base URLs, for those odd cases where your API is different from the others we use.\n\n\n```typescript\nimport { Collections } from '@dabapps/redux-api-collections';\nconst collections = Collections\u003cCollections, Items\u003e(collectionToRecordMapping, itemToRecordMapping, { baseUrl: '/another-base-url/' });\n```\n\n\n### Help, I need to run some form of custom step on Collections/Reducers and make it react to a specific action\n\nYou can provide custom Reducers that will be called by the built-in ones for post-processing the state when actions come in.\n\n```typescript\nimport { Collections } from '@dabapps/redux-api-collections';\n\nfunction myCustomCollectionReducer(state: CollectionStore\u003cCollections\u003e, action: AnyAction): CollectionStore\u003cCollections\u003e {\n  // Usual Reducer stuff\n}\n\nfunction myCustomItemReducer(state: ItemStore\u003cItems\u003e, action: AnyAction): ItemStore\u003cItems\u003e {\n  // More reducer stuff\n}\n\nconst collections = Collections\u003cCollections, Items\u003e(collectionToRecordMapping, itemToRecordMapping, {\n  collectionReducerPlugin: myCustomCollectionReducer,\n  itemReducerPlugin: myCustomItemReducer\n});\n```\n\n\n## Code of conduct\n\nFor guidelines regarding the code of conduct when contributing to this repository please review [https://www.dabapps.com/open-source/code-of-conduct/](https://www.dabapps.com/open-source/code-of-conduct/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdabapps%2Fredux-api-collections","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdabapps%2Fredux-api-collections","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdabapps%2Fredux-api-collections/lists"}