{"id":26325998,"url":"https://github.com/changelab-hq/jason","last_synced_at":"2026-04-20T05:02:04.998Z","repository":{"id":44921113,"uuid":"316701779","full_name":"changelab-hq/jason","owner":"changelab-hq","description":"Make realtime Rails + React applications easier and faster to write","archived":false,"fork":false,"pushed_at":"2023-11-23T13:35:35.000Z","size":298,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-13T22:47:47.640Z","etag":null,"topics":["actioncable","rails","react","redux-store"],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/changelab-hq.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-28T09:46:25.000Z","updated_at":"2022-01-18T12:46:50.000Z","dependencies_parsed_at":"2024-08-05T18:35:21.867Z","dependency_job_id":null,"html_url":"https://github.com/changelab-hq/jason","commit_stats":null,"previous_names":["jamesr2323/jason"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/changelab-hq/jason","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/changelab-hq%2Fjason","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/changelab-hq%2Fjason/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/changelab-hq%2Fjason/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/changelab-hq%2Fjason/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/changelab-hq","download_url":"https://codeload.github.com/changelab-hq/jason/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/changelab-hq%2Fjason/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32033717,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-20T00:18:06.643Z","status":"online","status_checked_at":"2026-04-20T02:00:06.527Z","response_time":94,"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":["actioncable","rails","react","redux-store"],"created_at":"2025-03-15T19:28:48.986Z","updated_at":"2026-04-20T05:02:04.980Z","avatar_url":"https://github.com/changelab-hq.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Jason\n\nJason is still in an experimental phase with a rapidly changing API. It is being used in some production applications, however it is still in 0.x.x series versions, which means that any 0.x version bump could introduce breaking changes.\n\n## The goal\n\nWe wanted:\n - Automatic updates to client state based on database state\n - Persistence to the database without many layers of passing parameters\n - Redux for awesome state management\n - Optimistic updates\n\nWe also wanted to avoid writing essentially the same code multiple times in different places to handle common CRUD-like operations. Combine Rails schema definition files, REST endpoints, Redux actions, stores, reducers, handlers for websocket payloads and the translations between them, and it adds up to tons of repetitive boilerplate. Every change to the data schema requires updates in five or six files. This inhibits refactoring and makes mistakes more likely.\n\nJason attempts to minimize this repitition by auto-generating API endpoints, redux stores and actions from a single schema definition. Further it adds listeners to ActiveRecord models allowing the redux store to be subscribed to updates from a model or set of models.\n\nAn alternative way of thinking about Jason is \"what if we applied the Flux/Redux state update pattern to make the _database_ the store?\".\n\n## Installation\n\nAdd the gem and the NPM package\n\n```ruby\ngem 'jason-rails', require: 'jason'\n```\n\n```bash\n  yarn add @jamesr2323/jason\n```\n\nYou will also need have peer dependencies of `redux`, `react-redux` and `@reduxjs/toolkit`.\n\n### In Rails\n\nInclude the module `Jason::Publisher` in all models you want to publish via Jason.\n\nCreate a new initializer e.g. `jason.rb` which defines your schema\n\n```ruby\nJason.setup do |config|\n  config.schema = {\n    post: {\n      subscribed_fields: [:id, :name]\n    },\n    comment: {\n      subscribed_fields: [:id]\n    },\n    user: {\n      subscribed_fields: [:id]\n    }\n  }\nend\n```\n\nMount the Jason engine in `routes.rb`\n```ruby\nmount Jason::Engine =\u003e \"/jason\"\n```\n\n### In your frontend code\n\nFirst you need to wrap your root component in a `JasonProvider`.\n\n```jsx\nimport { JasonProvider } from '@jamesr2323/jason'\n\nreturn \u003cJasonProvider\u003e\n  \u003cYourApp /\u003e\n\u003c/JasonProvider\u003e\n```\n\nThis is a wrapper around `react-redux` Provider component. This accepts the following props (all optional):\n\n- `reducers` - An object of reducers that will be included in `configureStore`. Make sure these do not conflict with the names of any of the models you are configuring for use with Jason\n- `extraActions` - Extra actions you want to be available via the `useAct` hook. (See below)\nThis must be a function which returns an object which will be merged with the main Jason actions. The function will be passed a dispatch function, store, axios instance and the Jason actions. For example you can add actions for one of your custom slices:\n\n```js\nfunction extraActions(dispatch, store, restClient, act) {\n  return {\n    local: {\n      upsert: payload =\u003e dis({ type: 'local/upsert', payload })\n    }\n  }\n}\n```\n\n- `middleware` - Passed directly to `configureStore` with additional Jason middleware\n\n## Usage\nJason provides three custom hooks to access functionality.\n\n### useAct\nThis returns an object which allows you to access actions which both update models on the server, and perform an optimistic update to the Redux store.\n\nExample\n```jsx\nimport React, { useState } from 'react'\nimport { useAct } from '@jamesr2323/jason'\n\nexport default function PostCreator() {\n  const act = useAct()\n  const [name, setName] = useState('')\n\n  function handleClick() {\n    act.posts.add({ name })\n  }\n\n  return \u003cdiv\u003e\n    \u003cinput value={name} onChange={e =\u003e setName(e.target.value)} /\u003e\n    \u003cbutton onClick={handleClick}\u003eAdd\u003c/button\u003e\n  \u003c/div\u003e\n}\n```\n\n### useSub\nThis subscribes your Redux store to a model or set of models. It will automatically unsubscribe when the component unmounts.\n\nExample\n```jsx\nimport React from 'react'\nimport { useSelector } from 'react-redux'\nimport { useSub } from '@jamesr2323/jason'\nimport _ from 'lodash'\n\nexport default function PostsList() {\n  useSub({ model: 'post', includes: ['comments'] })\n  const posts = useSelector(s =\u003e _.values(s.posts.entities))\n\n  return \u003cdiv\u003e\n    { posts.map(({ id, name }) =\u003e \u003cdiv key={id}\u003e{ name }\u003c/div\u003e) }\n  \u003c/div\u003e\n}\n```\n\n### useEager\nJason stores all the data in a normalized form - one redux slice per model. Often you might want to get nested data from several slices for use in components. The `useEager` hook provides an API for doing that. Under the hood it's just a wrapper around useSelector, which aims to mimic the behaviour of Rails eager loading.\n\nExample\nThis will fetch the comment as well as the post and user linked to it.\n\n```jsx\nimport React from 'react'\nimport { useSelector } from 'react-redux'\nimport { useEager } from '@jamesr2323/jason'\nimport _ from 'lodash'\n\nexport default function Comment({ id }) {\n  const comment = useEager('comments', id, ['post', 'user'])\n\n  return \u003cdiv\u003e\n    \u003cp\u003e{ comment.body }\u003c/p\u003e\n    \u003cp\u003eMade on post { comment.post.name } by { comment.user.name }\u003c/p\u003e\n  \u003c/div\u003e\n}\n```\n\n## Authorization\n\nBy default all models can be subscribed to and updated without authentication or authorization. Probably you want to lock down access. At the moment Jason has no opinion on how to handle authorization, it simply forwards parameters to a service that you provide - so the implementation can be as simple or as complex as you need.\n\n### Authorizing subscriptions\nYou can do this by providing an class to Jason in the initializer under the `subscription_authorization_service` key. This must be a class receiving a message `call` with the parameters `user`, `model`, `conditions`, `sub_models` and return true or false for whether the user is allowed to access a subscription with those parameters. You can decide the implementation details of this to be as simple or complex as your app requires.\n\n### Authorizing updates\nSimilarly to authorizing subscriptions, you can do this by providing an class to Jason in the initializer under the `update_authorization_service` key. This must be a class receiving a message `call` with the parameters `user`, `model`, `action`, `instance`, `params`  and return true or false for whether the user is allowed to make this update.\n\nSee the specs for some examples of this.\n\n## Roadmap\n\nDevelopment is primarily driven by the needs of projects we're using Jason in. In no particular order, being considered is:\n- Better detection of when subscriptions drop, delete subscription\n- Failure handling - rolling back local state in case of an error on the server\n- Authorization - more thorough authorization integration, with utility functions for common authorizations. Allowing authorization of access to particular fields such as restricting the fields of a user that are publicly broadcast.\n- Utilities for \"Draft editing\" - both storing client-side copies of model trees which can be committed or discarded, as well as persisting a shadow copy to the database (to allow resumable editing, or possibly collaborative editing features)\n- Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool\n- Assess using RedisGraph for the graph diffing functionality, to see if this would provide a performance boost\n- Improve the Typescript definitions (ie remove the abundant `any` typing currently used)\n\n## Publishing a new version\n- Update `version.rb`\n- Update CHANGELOG\n- `gem build`\n- `gem push`\n- `npm version [major/minor/patch]`\n- `npm publish`\n- Push new version to Github\n\n## License\n\nThe gem, npm package and source code in the git repository are available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchangelab-hq%2Fjason","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchangelab-hq%2Fjason","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchangelab-hq%2Fjason/lists"}