jason-rails 0.5.0 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/Gemfile.lock +1 -1
- data/README.md +141 -5
- data/app/controllers/jason/api/pusher_controller.rb +15 -0
- data/app/controllers/jason/api_controller.rb +46 -4
- data/client/lib/JasonContext.d.ts +1 -1
- data/client/lib/JasonContext.js +4 -1
- data/client/lib/JasonProvider.js +1 -1
- data/client/lib/createJasonReducers.js +7 -0
- data/client/lib/createPayloadHandler.d.ts +6 -3
- data/client/lib/createPayloadHandler.js +8 -4
- data/client/lib/createTransportAdapter.d.ts +5 -0
- data/client/lib/createTransportAdapter.js +20 -0
- data/client/lib/index.d.ts +2 -0
- data/client/lib/index.js +3 -1
- data/client/lib/makeEager.js +2 -2
- data/client/lib/pruneIdsMiddleware.js +9 -11
- data/client/lib/restClient.d.ts +1 -1
- data/client/lib/transportAdapters/actionCableAdapter.d.ts +5 -0
- data/client/lib/transportAdapters/actionCableAdapter.js +35 -0
- data/client/lib/transportAdapters/pusherAdapter.d.ts +5 -0
- data/client/lib/transportAdapters/pusherAdapter.js +68 -0
- data/client/lib/useEager.d.ts +1 -0
- data/client/lib/useEager.js +12 -0
- data/client/lib/useJason.js +30 -35
- data/client/lib/useJason.test.js +8 -2
- data/client/lib/useSub.d.ts +1 -1
- data/client/lib/useSub.js +5 -3
- data/client/package.json +2 -1
- data/client/src/JasonContext.ts +4 -1
- data/client/src/JasonProvider.tsx +1 -1
- data/client/src/createJasonReducers.ts +7 -0
- data/client/src/createPayloadHandler.ts +9 -4
- data/client/src/createTransportAdapter.ts +13 -0
- data/client/src/index.ts +3 -1
- data/client/src/makeEager.ts +2 -2
- data/client/src/pruneIdsMiddleware.ts +11 -11
- data/client/src/restClient.ts +2 -1
- data/client/src/transportAdapters/actionCableAdapter.ts +38 -0
- data/client/src/transportAdapters/pusherAdapter.ts +72 -0
- data/client/src/useEager.ts +9 -0
- data/client/src/useJason.test.ts +8 -2
- data/client/src/useJason.ts +31 -36
- data/client/src/useSub.ts +5 -3
- data/client/yarn.lock +12 -0
- data/config/routes.rb +5 -1
- data/lib/jason.rb +56 -8
- data/lib/jason/broadcaster.rb +19 -0
- data/lib/jason/channel.rb +10 -3
- data/lib/jason/graph_helper.rb +165 -0
- data/lib/jason/includes_helper.rb +108 -0
- data/lib/jason/lua_generator.rb +23 -1
- data/lib/jason/publisher.rb +21 -17
- data/lib/jason/subscription.rb +208 -179
- data/lib/jason/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e62faa4e4f1246fa42810c324eec1f8fe96b8026ccdbe12f5b8bd7bc2df3bd4a
|
4
|
+
data.tar.gz: e4f829ce397c7192c173ba6e712a4b5873bd065d687cebf5ad72025f0436f51a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e3abfbfd6e0fbbe6c40dc7c0540448091cd440aaeec3580951a6148d6f1f92e61d5808b39ca02e8c564e1aa57beaf1de4d4cddb34e27b96ace09ca61d856d9a
|
7
|
+
data.tar.gz: b58a34f35981aea9b991f036cb83f9e059fa17a5932d9e9df78b6e86b5532484fe0dc115531365d5044d2f7df42355b5481a353233dcbd6744077b8fcbd58f18
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# Jason
|
2
2
|
|
3
|
+
Jason 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.
|
4
|
+
|
3
5
|
## The goal
|
4
6
|
|
5
7
|
I wanted:
|
6
8
|
- Automatic updates to client state based on database state
|
7
|
-
-
|
9
|
+
- Persistence to the database without many layers of passing parameters
|
8
10
|
- Redux for awesome state management
|
9
11
|
- Optimistic updates
|
10
12
|
|
@@ -12,6 +14,8 @@ I also wanted to avoid writing essentially the same code multiple times in diffe
|
|
12
14
|
|
13
15
|
Jason 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.
|
14
16
|
|
17
|
+
An alternative way of thinking about Jason is "what if we applied the Flux/Redux state update pattern to make the _database_ the store?".
|
18
|
+
|
15
19
|
## Installation
|
16
20
|
|
17
21
|
Add the gem and the NPM package
|
@@ -24,22 +28,154 @@ gem 'jason-rails'
|
|
24
28
|
yarn add @jamesr2323/jason
|
25
29
|
```
|
26
30
|
|
31
|
+
You will also need have peer dependencies of `redux`, `react-redux` and `@reduxjs/toolkit`.
|
32
|
+
|
33
|
+
### In Rails
|
34
|
+
|
35
|
+
Include the module `Jason::Publisher` in all models you want to publish via Jason.
|
36
|
+
|
37
|
+
Create a new initializer e.g. `jason.rb` which defines your schema
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
Jason.setup do |config|
|
41
|
+
config.schema = {
|
42
|
+
post: {
|
43
|
+
subscribed_fields: [:id, :name]
|
44
|
+
},
|
45
|
+
comment: {
|
46
|
+
subscribed_fields: [:id]
|
47
|
+
},
|
48
|
+
user: {
|
49
|
+
subscribed_fields: [:id]
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
### In your frontend code
|
56
|
+
|
57
|
+
First you need to wrap your root component in a `JasonProvider`.
|
58
|
+
|
59
|
+
```jsx
|
60
|
+
import { JasonProvider } from '@jamesr2323/jason'
|
61
|
+
|
62
|
+
return <JasonProvider>
|
63
|
+
<YourApp />
|
64
|
+
</JasonProvider>
|
65
|
+
```
|
66
|
+
|
67
|
+
This is a wrapper around `react-redux` Provider component. This accepts the following props (all optional):
|
68
|
+
|
69
|
+
- `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
|
70
|
+
- `extraActions` - Extra actions you want to be available via the `useAct` hook. (See below)
|
71
|
+
This 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:
|
72
|
+
|
73
|
+
```js
|
74
|
+
function extraActions(dispatch, store, restClient, act) {
|
75
|
+
return {
|
76
|
+
local: {
|
77
|
+
upsert: payload => dis({ type: 'local/upsert', payload })
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
```
|
82
|
+
|
83
|
+
- `middleware` - Passed directly to `configureStore` with additional Jason middleware
|
84
|
+
|
27
85
|
## Usage
|
86
|
+
Jason provides three custom hooks to access functionality.
|
87
|
+
|
88
|
+
### useAct
|
89
|
+
This 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.
|
90
|
+
|
91
|
+
Example
|
92
|
+
```jsx
|
93
|
+
import React, { useState } from 'react'
|
94
|
+
import { useAct } from '@jamesr2323/jason'
|
28
95
|
|
29
|
-
|
96
|
+
export default function PostCreator() {
|
97
|
+
const act = useAct()
|
98
|
+
const [name, setName] = useState('')
|
30
99
|
|
100
|
+
function handleClick() {
|
101
|
+
act.posts.add({ name })
|
102
|
+
}
|
103
|
+
|
104
|
+
return <div>
|
105
|
+
<input value={name} onChange={e => setName(e.target.value)} />
|
106
|
+
<button onClick={handleClick}>Add</button>
|
107
|
+
</div>
|
108
|
+
}
|
109
|
+
```
|
110
|
+
|
111
|
+
### useSub
|
112
|
+
This subscribes your Redux store to a model or set of models. It will automatically unsubscribe when the component unmounts.
|
113
|
+
|
114
|
+
Example
|
115
|
+
```jsx
|
116
|
+
import React from 'react'
|
117
|
+
import { useSelector } from 'react-redux'
|
118
|
+
import { useSub } from '@jamesr2323/jason'
|
119
|
+
import _ from 'lodash'
|
120
|
+
|
121
|
+
export default function PostsList() {
|
122
|
+
useSub({ model: 'post', includes: ['comments'] })
|
123
|
+
const posts = useSelector(s => _.values(s.posts.entities))
|
124
|
+
|
125
|
+
return <div>
|
126
|
+
{ posts.map(({ id, name }) => <div key={id}>{ name }</div>) }
|
127
|
+
</div>
|
128
|
+
}
|
129
|
+
```
|
130
|
+
|
131
|
+
### useEager
|
132
|
+
Jason 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.
|
133
|
+
|
134
|
+
Example
|
135
|
+
This will fetch the comment as well as the post and user linked to it.
|
136
|
+
|
137
|
+
```jsx
|
138
|
+
import React from 'react'
|
139
|
+
import { useSelector } from 'react-redux'
|
140
|
+
import { useEager } from '@jamesr2323/jason'
|
141
|
+
import _ from 'lodash'
|
142
|
+
|
143
|
+
export default function Comment({ id }) {
|
144
|
+
const comment = useEager('comments', id, ['post', 'user'])
|
145
|
+
|
146
|
+
return <div>
|
147
|
+
<p>{ comment.body }</p>
|
148
|
+
<p>Made on post { comment.post.name } by { comment.user.name }</p>
|
149
|
+
</div>
|
150
|
+
}
|
151
|
+
```
|
152
|
+
|
153
|
+
## Authorization
|
154
|
+
|
155
|
+
By default all models can be subscribed to and updated without authentication or authorization. Probably you want to lock down access.
|
156
|
+
|
157
|
+
### Authorizing subscriptions
|
158
|
+
You 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.
|
159
|
+
|
160
|
+
### Authorizing updates
|
161
|
+
Similarly 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`, `instance`, `update`, `remove` and return true or false for whether the user is allowed to access a subscription with those parameters.
|
162
|
+
|
163
|
+
## Roadmap
|
164
|
+
|
165
|
+
Development is primarily driven by the needs of projects we're using Jason in. In no particular order, being considered is:
|
166
|
+
- Failure handling - rolling back local state in case of an error on the server
|
167
|
+
- 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.
|
168
|
+
- 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)
|
169
|
+
- Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool
|
31
170
|
|
32
171
|
## Development
|
33
172
|
|
34
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
35
173
|
|
36
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
37
174
|
|
38
175
|
## Contributing
|
39
176
|
|
40
177
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jason. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/jason/blob/master/CODE_OF_CONDUCT.md).
|
41
178
|
|
42
|
-
|
43
179
|
## License
|
44
180
|
|
45
181
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Jason::Api::PusherController < ApplicationController
|
2
|
+
skip_before_action :verify_authenticity_token
|
3
|
+
|
4
|
+
def auth
|
5
|
+
channel_main_name = params[:channel_name].remove("private-#{Jason.pusher_channel_prefix}-")
|
6
|
+
subscription_id = channel_main_name.remove('jason-')
|
7
|
+
|
8
|
+
if Jason::Subscription.find_by_id(subscription_id).user_can_access?(current_user)
|
9
|
+
response = Pusher.authenticate(params[:channel_name], params[:socket_id])
|
10
|
+
return render json: response
|
11
|
+
else
|
12
|
+
return head :forbidden
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,6 +1,21 @@
|
|
1
1
|
class Jason::ApiController < ::ApplicationController
|
2
|
-
|
3
|
-
|
2
|
+
before_action :load_and_authorize_subscription, only: [:create_subscription, :remove_subscription, :get_payload]
|
3
|
+
# config seems to be a reserved name, resulting in infinite loop
|
4
|
+
def configuration
|
5
|
+
payload = {
|
6
|
+
schema: Jason.schema,
|
7
|
+
transportService: Jason.transport_service,
|
8
|
+
}
|
9
|
+
|
10
|
+
if Jason.transport_service == :pusher
|
11
|
+
payload.merge!({
|
12
|
+
pusherKey: Jason.pusher_key,
|
13
|
+
pusherRegion: Jason.pusher_region,
|
14
|
+
pusherChannelPrefix: Jason.pusher_channel_prefix
|
15
|
+
})
|
16
|
+
end
|
17
|
+
|
18
|
+
render json: payload
|
4
19
|
end
|
5
20
|
|
6
21
|
def action
|
@@ -20,10 +35,10 @@ class Jason::ApiController < ::ApplicationController
|
|
20
35
|
all_instance_ids.insert(priority.to_i, instance.id)
|
21
36
|
|
22
37
|
all_instance_ids.each_with_index do |id, i|
|
23
|
-
model.find(id).update!(priority: i
|
38
|
+
model.find(id).update!(priority: i)
|
24
39
|
end
|
25
40
|
|
26
|
-
model.
|
41
|
+
model.find(all_instance_ids).each(&:force_publish_json)
|
27
42
|
elsif action == 'upsert' || action == 'add'
|
28
43
|
payload = api_model.permit(params)
|
29
44
|
return render json: model.find_or_create_by_id!(payload).as_json(api_model.as_json_config)
|
@@ -33,4 +48,31 @@ class Jason::ApiController < ::ApplicationController
|
|
33
48
|
|
34
49
|
return head :ok
|
35
50
|
end
|
51
|
+
|
52
|
+
def create_subscription
|
53
|
+
@subscription.add_consumer(params[:consumer_id])
|
54
|
+
render json: { channelName: @subscription.channel }
|
55
|
+
end
|
56
|
+
|
57
|
+
def remove_subscription
|
58
|
+
@subscription.remove_consumer(params[:consumer_id])
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_payload
|
62
|
+
if params[:options].try(:[], :force_refresh)
|
63
|
+
@subscription.set_ids_for_sub_models
|
64
|
+
end
|
65
|
+
|
66
|
+
render json: @subscription.get
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def load_and_authorize_subscription
|
72
|
+
config = params[:config].to_unsafe_h
|
73
|
+
@subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
|
74
|
+
if !@subscription.user_can_access?(current_user)
|
75
|
+
return head :forbidden
|
76
|
+
end
|
77
|
+
end
|
36
78
|
end
|
data/client/lib/JasonContext.js
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
const react_1 = require("react");
|
4
|
-
const
|
4
|
+
const eager = function (entity, id, relations) {
|
5
|
+
console.error("Eager called but is not implemented");
|
6
|
+
};
|
7
|
+
const context = react_1.createContext({ actions: {}, subscribe: null, eager });
|
5
8
|
exports.default = context;
|
data/client/lib/JasonProvider.js
CHANGED
@@ -8,7 +8,7 @@ const useJason_1 = __importDefault(require("./useJason"));
|
|
8
8
|
const react_redux_1 = require("react-redux");
|
9
9
|
const JasonContext_1 = __importDefault(require("./JasonContext"));
|
10
10
|
const JasonProvider = ({ reducers, middleware, extraActions, children }) => {
|
11
|
-
const [store, value
|
11
|
+
const [store, value] = useJason_1.default({ reducers, middleware, extraActions });
|
12
12
|
if (!(store && value))
|
13
13
|
return react_1.default.createElement("div", null); // Wait for async fetch of schema to complete
|
14
14
|
return react_1.default.createElement(react_redux_1.Provider, { store: store },
|
@@ -42,6 +42,7 @@ function generateJasonSlices(models) {
|
|
42
42
|
setSubscriptionIds(s, a) {
|
43
43
|
const { payload } = a;
|
44
44
|
const { subscriptionId, model, ids } = payload;
|
45
|
+
console.log({ initialState });
|
45
46
|
s[model][subscriptionId] = ids;
|
46
47
|
},
|
47
48
|
addSubscriptionId(s, a) {
|
@@ -53,6 +54,12 @@ function generateJasonSlices(models) {
|
|
53
54
|
const { payload } = a;
|
54
55
|
const { subscriptionId, model, id } = payload;
|
55
56
|
s[model][subscriptionId] = lodash_1.default.remove(s[model][subscriptionId] || [], id);
|
57
|
+
},
|
58
|
+
removeSubscription(s, a) {
|
59
|
+
const { payload: { subscriptionId } } = a;
|
60
|
+
lodash_1.default.map(models, model => {
|
61
|
+
delete s[model][subscriptionId];
|
62
|
+
});
|
56
63
|
}
|
57
64
|
}
|
58
65
|
}).reducer;
|
@@ -1,6 +1,9 @@
|
|
1
|
-
export default function createPayloadHandler({ dispatch, serverActionQueue,
|
1
|
+
export default function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config }: {
|
2
2
|
dispatch: any;
|
3
3
|
serverActionQueue: any;
|
4
|
-
|
4
|
+
transportAdapter: any;
|
5
5
|
config: any;
|
6
|
-
}):
|
6
|
+
}): {
|
7
|
+
handlePayload: (data: any) => void;
|
8
|
+
tearDown: () => void;
|
9
|
+
};
|
@@ -11,7 +11,7 @@ function diffSeconds(dt2, dt1) {
|
|
11
11
|
var diff = (dt2.getTime() - dt1.getTime()) / 1000;
|
12
12
|
return Math.abs(Math.round(diff));
|
13
13
|
}
|
14
|
-
function createPayloadHandler({ dispatch, serverActionQueue,
|
14
|
+
function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config }) {
|
15
15
|
const subscriptionId = uuid_1.v4();
|
16
16
|
let idx = {};
|
17
17
|
let patchQueue = {};
|
@@ -19,7 +19,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
|
|
19
19
|
let updateDeadline = null;
|
20
20
|
let checkInterval;
|
21
21
|
function getPayload() {
|
22
|
-
setTimeout(() =>
|
22
|
+
setTimeout(() => transportAdapter.getPayload(config), 1000);
|
23
23
|
}
|
24
24
|
function camelizeKeys(item) {
|
25
25
|
return deepCamelizeKeys_1.default(item, key => uuid_1.validate(key));
|
@@ -41,7 +41,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
|
|
41
41
|
dispatch({ type: `jasonModels/setSubscriptionIds`, payload: { model, subscriptionId, ids } });
|
42
42
|
}
|
43
43
|
else if (destroy) {
|
44
|
-
|
44
|
+
// Middleware will determine if this model should be removed if it isn't in any other subscriptions
|
45
45
|
dispatch({ type: `jasonModels/removeSubscriptionId`, payload: { model, subscriptionId, id } });
|
46
46
|
}
|
47
47
|
else {
|
@@ -87,6 +87,10 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
|
|
87
87
|
}
|
88
88
|
}
|
89
89
|
tGetPayload();
|
90
|
-
|
90
|
+
// Clean up after ourselves
|
91
|
+
function tearDown() {
|
92
|
+
dispatch({ type: `jasonModels/removeSubscription`, payload: { subscriptionId } });
|
93
|
+
}
|
94
|
+
return { handlePayload, tearDown };
|
91
95
|
}
|
92
96
|
exports.default = createPayloadHandler;
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const actionCableAdapter_1 = __importDefault(require("./transportAdapters/actionCableAdapter"));
|
7
|
+
const pusherAdapter_1 = __importDefault(require("./transportAdapters/pusherAdapter"));
|
8
|
+
function createTransportAdapter(jasonConfig, handlePayload, dispatch, onConnect) {
|
9
|
+
const { transportService } = jasonConfig;
|
10
|
+
if (transportService === 'action_cable') {
|
11
|
+
return actionCableAdapter_1.default(jasonConfig, handlePayload, dispatch, onConnect);
|
12
|
+
}
|
13
|
+
else if (transportService === 'pusher') {
|
14
|
+
return pusherAdapter_1.default(jasonConfig, handlePayload, dispatch);
|
15
|
+
}
|
16
|
+
else {
|
17
|
+
throw (`Transport adapter does not exist for ${transportService}`);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
exports.default = createTransportAdapter;
|
data/client/lib/index.d.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
/// <reference types="react" />
|
2
2
|
import _useAct from './useAct';
|
3
3
|
import _useSub from './useSub';
|
4
|
+
import _useEager from './useEager';
|
4
5
|
export declare const JasonProvider: ({ reducers, middleware, extraActions, children }: {
|
5
6
|
reducers?: any;
|
6
7
|
middleware?: any;
|
@@ -9,3 +10,4 @@ export declare const JasonProvider: ({ reducers, middleware, extraActions, child
|
|
9
10
|
}) => JSX.Element;
|
10
11
|
export declare const useAct: typeof _useAct;
|
11
12
|
export declare const useSub: typeof _useSub;
|
13
|
+
export declare const useEager: typeof _useEager;
|