jason-rails 0.6.6 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/Gemfile.lock +11 -4
- data/README.md +9 -15
- data/app/controllers/jason/{api_controller.rb → jason_controller.rb} +27 -5
- data/app/controllers/jason/{api/pusher_controller.rb → pusher_controller.rb} +1 -1
- data/app/workers/jason/outbound_message_queue_worker.rb +21 -0
- data/client/lib/JasonProvider.d.ts +2 -1
- data/client/lib/JasonProvider.js +2 -2
- data/client/lib/addRelations.d.ts +1 -0
- data/client/lib/addRelations.js +39 -0
- data/client/lib/createJasonReducers.js +4 -2
- data/client/lib/createOptDis.d.ts +1 -1
- data/client/lib/createOptDis.js +9 -8
- data/client/lib/createServerActionQueue.d.ts +3 -2
- data/client/lib/createServerActionQueue.js +32 -6
- data/client/lib/createServerActionQueue.test.js +61 -6
- data/client/lib/createThenable.d.ts +1 -0
- data/client/lib/createThenable.js +5 -0
- data/client/lib/index.d.ts +7 -1
- data/client/lib/index.js +3 -1
- data/client/lib/transportAdapters/actionCableAdapter.js +24 -4
- data/client/lib/transportAdapters/pusherAdapter.js +1 -1
- data/client/lib/useDraft.d.ts +1 -0
- data/client/lib/useDraft.js +13 -0
- data/client/lib/useEager.d.ts +1 -1
- data/client/lib/useEager.js +10 -5
- data/client/lib/useJason.d.ts +2 -1
- data/client/lib/useJason.js +4 -6
- data/client/package.json +1 -1
- data/client/src/JasonProvider.tsx +2 -2
- data/client/src/addRelations.ts +33 -0
- data/client/src/createJasonReducers.ts +4 -2
- data/client/src/createOptDis.ts +10 -8
- data/client/src/createServerActionQueue.test.ts +60 -6
- data/client/src/createServerActionQueue.ts +41 -6
- data/client/src/index.ts +2 -0
- data/client/src/transportAdapters/actionCableAdapter.ts +24 -5
- data/client/src/transportAdapters/pusherAdapter.ts +1 -2
- data/client/src/useDraft.ts +17 -0
- data/client/src/useEager.ts +9 -6
- data/client/src/useJason.ts +3 -6
- data/config/routes.rb +6 -6
- data/jason-rails.gemspec +1 -0
- data/lib/jason.rb +6 -1
- data/lib/jason/api_model.rb +0 -4
- data/lib/jason/broadcaster.rb +2 -1
- data/lib/jason/channel.rb +0 -7
- data/lib/jason/conditions_matcher.rb +88 -0
- data/lib/jason/consistency_checker.rb +65 -0
- data/lib/jason/graph_helper.rb +4 -0
- data/lib/jason/publisher.rb +42 -38
- data/lib/jason/subscription.rb +63 -18
- data/lib/jason/version.rb +1 -1
- metadata +29 -7
- data/client/src/makeEager.ts +0 -46
- data/lib/jason/publisher_old.rb +0 -112
- data/lib/jason/subscription_old.rb +0 -171
data/lib/jason/publisher_old.rb
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
module Jason::PublisherOld
|
2
|
-
extend ActiveSupport::Concern
|
3
|
-
|
4
|
-
def cache_json
|
5
|
-
as_json_config = api_model.as_json_config
|
6
|
-
scope = api_model.scope
|
7
|
-
|
8
|
-
if self.persisted? && (scope.blank? || self.class.unscoped.send(scope).exists?(self.id))
|
9
|
-
payload = self.reload.as_json(as_json_config)
|
10
|
-
$redis_jason.hset("jason:#{self.class.name.underscore}:cache", self.id, payload.to_json)
|
11
|
-
else
|
12
|
-
$redis_jason.hdel("jason:#{self.class.name.underscore}:cache", self.id)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def publish_json
|
17
|
-
cache_json
|
18
|
-
return if skip_publish_json
|
19
|
-
|
20
|
-
self.class.jason_subscriptions.each do |id, config_json|
|
21
|
-
config = JSON.parse(config_json)
|
22
|
-
|
23
|
-
if (config['conditions'] || {}).all? { |field, value| self.send(field) == value }
|
24
|
-
Jason::Subscription.new(id: id).update(self.class.name.underscore)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def publish_json_if_changed
|
30
|
-
subscribed_fields = api_model.subscribed_fields
|
31
|
-
publish_json if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present? || !self.persisted?
|
32
|
-
end
|
33
|
-
|
34
|
-
class_methods do
|
35
|
-
def subscriptions
|
36
|
-
$redis_jason.hgetall("jason:#{self.name.underscore}:subscriptions")
|
37
|
-
end
|
38
|
-
|
39
|
-
def jason_subscriptions
|
40
|
-
$redis_jason.hgetall("jason:#{self.name.underscore}:subscriptions")
|
41
|
-
end
|
42
|
-
|
43
|
-
def publish_all(instances)
|
44
|
-
instances.each(&:cache_json)
|
45
|
-
|
46
|
-
subscriptions.each do |id, config_json|
|
47
|
-
Jason::Subscription.new(id: id).update(self.name.underscore)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def flush_cache
|
52
|
-
$redis_jason.del("jason:#{self.name.underscore}:cache")
|
53
|
-
end
|
54
|
-
|
55
|
-
def setup_json
|
56
|
-
self.after_initialize -> {
|
57
|
-
@api_model = Jason::ApiModel.new(self.class.name.underscore)
|
58
|
-
}
|
59
|
-
self.after_commit :publish_json_if_changed
|
60
|
-
|
61
|
-
include_models = Jason::ApiModel.new(self.name.underscore).include_models
|
62
|
-
|
63
|
-
include_models.map do |assoc|
|
64
|
-
puts assoc
|
65
|
-
reflection = self.reflect_on_association(assoc.to_sym)
|
66
|
-
reflection.klass.after_commit -> {
|
67
|
-
subscribed_fields = Jason::ApiModel.new(self.class.name.underscore).subscribed_fields
|
68
|
-
puts subscribed_fields.inspect
|
69
|
-
|
70
|
-
if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present?
|
71
|
-
self.send(reflection.inverse_of.name)&.publish_json
|
72
|
-
end
|
73
|
-
}
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def find_or_create_by_id(params)
|
78
|
-
object = find_by(id: params[:id])
|
79
|
-
|
80
|
-
if object
|
81
|
-
object.update(params)
|
82
|
-
elsif params[:hidden]
|
83
|
-
return false ## If an object is passed with hidden = true but didn't already exist, it's safe to never create it
|
84
|
-
else
|
85
|
-
object = create!(params)
|
86
|
-
end
|
87
|
-
|
88
|
-
object
|
89
|
-
end
|
90
|
-
|
91
|
-
def find_or_create_by_id!(params)
|
92
|
-
object = find_by(id: params[:id])
|
93
|
-
|
94
|
-
if object
|
95
|
-
object.update!(params)
|
96
|
-
elsif params[:hidden]
|
97
|
-
## TODO: We're diverging from semantics of the Rails bang! methods here, which would normally either raise or return an object. Find a way to make this better.
|
98
|
-
return false ## If an object is passed with hidden = true but didn't already exist, it's safe to never create it
|
99
|
-
else
|
100
|
-
object = create!(params)
|
101
|
-
end
|
102
|
-
|
103
|
-
object
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
included do
|
108
|
-
attr_accessor :skip_publish_json, :api_model
|
109
|
-
|
110
|
-
setup_json
|
111
|
-
end
|
112
|
-
end
|
@@ -1,171 +0,0 @@
|
|
1
|
-
class Jason::SubscriptionOld
|
2
|
-
attr_accessor :id, :config
|
3
|
-
|
4
|
-
def initialize(id: nil, config: nil)
|
5
|
-
if id
|
6
|
-
@id = id
|
7
|
-
raw_config = $redis_jason.hgetall("jason:subscriptions:#{id}").map { |k,v| [k, JSON.parse(v)] }.to_h
|
8
|
-
set_config(raw_config)
|
9
|
-
else
|
10
|
-
@id = Digest::MD5.hexdigest(config.to_json)
|
11
|
-
configure(config)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def set_config(raw_config)
|
16
|
-
@config = raw_config.with_indifferent_access.map { |k,v| [k.underscore.to_s, v] }.to_h
|
17
|
-
end
|
18
|
-
|
19
|
-
def configure(raw_config)
|
20
|
-
set_config(raw_config)
|
21
|
-
$redis_jason.hmset("jason:subscriptions:#{id}", *config.map { |k,v| [k, v.to_json]}.flatten)
|
22
|
-
end
|
23
|
-
|
24
|
-
def destroy
|
25
|
-
config.each do |model, value|
|
26
|
-
$redis_jason.srem("jason:#{model.to_s.underscore}:subscriptions", id)
|
27
|
-
end
|
28
|
-
$redis_jason.del("jason:subscriptions:#{id}")
|
29
|
-
end
|
30
|
-
|
31
|
-
def add_consumer(consumer_id)
|
32
|
-
before_consumer_count = consumer_count
|
33
|
-
$redis_jason.sadd("jason:subscriptions:#{id}:consumers", consumer_id)
|
34
|
-
$redis_jason.hset("jason:consumers", consumer_id, Time.now.utc)
|
35
|
-
|
36
|
-
add_subscriptions
|
37
|
-
publish_all
|
38
|
-
end
|
39
|
-
|
40
|
-
def remove_consumer(consumer_id)
|
41
|
-
$redis_jason.srem("jason:subscriptions:#{id}:consumers", consumer_id)
|
42
|
-
$redis_jason.hdel("jason:consumers", consumer_id)
|
43
|
-
|
44
|
-
if consumer_count == 0
|
45
|
-
remove_subscriptions
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def consumer_count
|
50
|
-
$redis_jason.scard("jason:subscriptions:#{id}:consumers")
|
51
|
-
end
|
52
|
-
|
53
|
-
def channel
|
54
|
-
"jason:#{id}"
|
55
|
-
end
|
56
|
-
|
57
|
-
def publish_all
|
58
|
-
config.each do |model, model_config|
|
59
|
-
klass = model.to_s.classify.constantize
|
60
|
-
conditions = model_config['conditions'] || {}
|
61
|
-
klass.where(conditions).find_each(&:cache_json)
|
62
|
-
update(model)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def add_subscriptions
|
67
|
-
config.each do |model, value|
|
68
|
-
$redis_jason.hset("jason:#{model.to_s.underscore}:subscriptions", id, value.to_json)
|
69
|
-
update(model)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def remove_subscriptions
|
74
|
-
config.each do |model, _|
|
75
|
-
$redis_jason.hdel("jason:#{model.to_s.underscore}:subscriptions", id)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.publish_all
|
80
|
-
JASON_API_MODEL.each do |model, _v|
|
81
|
-
klass = model.to_s.classify.constantize
|
82
|
-
klass.publish_all(klass.all) if klass.respond_to?(:publish_all)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def get(model_name)
|
87
|
-
LuaGenerator.new.index_hash_by_set("jason:cache:#{model_name}", "")
|
88
|
-
|
89
|
-
value = JSON.parse($redis_jason.get("#{channel}:#{model}:value") || '[]')
|
90
|
-
idx = $redis_jason.get("#{channel}:#{model}:idx").to_i
|
91
|
-
|
92
|
-
{
|
93
|
-
type: 'payload',
|
94
|
-
md5Hash: id,
|
95
|
-
model: model,
|
96
|
-
value: value,
|
97
|
-
idx: idx
|
98
|
-
}
|
99
|
-
end
|
100
|
-
|
101
|
-
def get_diff(old_value, value)
|
102
|
-
JsonDiff.generate(old_value, value)
|
103
|
-
end
|
104
|
-
|
105
|
-
def deep_stringify(value)
|
106
|
-
if value.is_a?(Hash)
|
107
|
-
value.deep_stringify_keys
|
108
|
-
elsif value.is_a?(Array)
|
109
|
-
value.map { |x| x.deep_stringify_keys }
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def get_throttle
|
114
|
-
if !$throttle_rate || !$throttle_timeout || Time.now.utc > $throttle_timeout
|
115
|
-
$throttle_timeout = Time.now.utc + 5.seconds
|
116
|
-
$throttle_rate = ($redis_jason.get('global_throttle_rate') || 0).to_i
|
117
|
-
else
|
118
|
-
$throttle_rate
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Atomically update and return patch
|
123
|
-
def update(model)
|
124
|
-
start_time = Time.now.utc
|
125
|
-
conditions = config[model]['conditions']
|
126
|
-
|
127
|
-
value = $redis_jason.hgetall("jason:#{model}:cache")
|
128
|
-
.values.map { |v| JSON.parse(v) }
|
129
|
-
.select { |v| (conditions || {}).all? { |field, value| v[field] == value } }
|
130
|
-
.sort_by { |v| v['id'] }
|
131
|
-
|
132
|
-
# lfsa = last finished, started at
|
133
|
-
# If another job that started after this one, finished before this one, skip sending this state update
|
134
|
-
if Time.parse($redis_jason.get("jason:#{channel}:lfsa") || '1970-01-01 00:00:00 UTC') < start_time
|
135
|
-
$redis_jason.set("jason:#{channel}:lfsa", start_time)
|
136
|
-
else
|
137
|
-
return
|
138
|
-
end
|
139
|
-
|
140
|
-
value = deep_stringify(value)
|
141
|
-
|
142
|
-
# If value has changed, return old value and new idx. Otherwise do nothing.
|
143
|
-
cmd = <<~LUA
|
144
|
-
local old_val=redis.call('get', ARGV[1] .. ':value')
|
145
|
-
if old_val ~= ARGV[2] then
|
146
|
-
redis.call('set', ARGV[1] .. ':value', ARGV[2])
|
147
|
-
local new_idx = redis.call('incr', ARGV[1] .. ':idx')
|
148
|
-
return { new_idx, old_val }
|
149
|
-
end
|
150
|
-
LUA
|
151
|
-
|
152
|
-
result = $redis_jason.eval cmd, [], ["#{channel}:#{model}", value.to_json]
|
153
|
-
return if result.blank?
|
154
|
-
|
155
|
-
idx = result[0]
|
156
|
-
old_value = JSON.parse(result[1] || '[]')
|
157
|
-
diff = get_diff(old_value, value)
|
158
|
-
|
159
|
-
end_time = Time.now.utc
|
160
|
-
|
161
|
-
payload = {
|
162
|
-
model: model,
|
163
|
-
md5Hash: id,
|
164
|
-
diff: diff,
|
165
|
-
idx: idx.to_i,
|
166
|
-
latency: ((end_time - start_time)*1000).round
|
167
|
-
}
|
168
|
-
|
169
|
-
ActionCable.server.broadcast("jason:#{id}", payload)
|
170
|
-
end
|
171
|
-
end
|