volt 0.9.5.pre3 → 0.9.5.pre4
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/CHANGELOG.md +2 -0
- data/app/volt/models/active_volt_instance.rb +6 -0
- data/app/volt/tasks/live_query/live_query.rb +2 -2
- data/app/volt/tasks/query_tasks.rb +1 -1
- data/lib/volt/models.rb +9 -11
- data/lib/volt/models/array_model.rb +80 -36
- data/lib/volt/models/field_helpers.rb +5 -1
- data/lib/volt/models/permissions.rb +81 -72
- data/lib/volt/models/persistors/array_store.rb +5 -25
- data/lib/volt/models/root_models/store_root.rb +0 -1
- data/lib/volt/reactive/reactive_array.rb +4 -1
- data/lib/volt/server/message_bus/peer_to_peer.rb +2 -2
- data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
- data/lib/volt/server/middleware/default_middleware_stack.rb +3 -2
- data/lib/volt/server/rack/asset_files.rb +18 -0
- data/lib/volt/server/rack/component_code.rb +1 -1
- data/lib/volt/server/rack/index_files.rb +2 -2
- data/lib/volt/server/rack/opal_files.rb +3 -0
- data/lib/volt/spec/setup.rb +0 -2
- data/lib/volt/utils/promise_extensions.rb +4 -3
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +5 -0
- data/lib/volt/volt/users.rb +2 -2
- data/spec/extra_core/class_spec.rb +11 -0
- data/spec/integration/bindings_spec.rb +0 -1
- data/spec/models/array_model_spec.rb +16 -0
- data/spec/models/field_helpers_spec.rb +8 -1
- data/spec/models/permissions_spec.rb +25 -0
- data/volt.gemspec +1 -1
- metadata +5 -5
- data/lib/volt/utils/promise.rb +0 -429
@@ -168,43 +168,23 @@ module Volt
|
|
168
168
|
end
|
169
169
|
|
170
170
|
# Call a method on the model once the model is loaded. Return a promise
|
171
|
-
# that will resolve
|
172
|
-
|
173
|
-
def run_once_loaded(block)
|
171
|
+
# that will resolve when the model is loaded
|
172
|
+
def run_once_loaded
|
174
173
|
promise = Promise.new
|
175
174
|
|
176
|
-
# call once the method is loaded.
|
177
|
-
model_loaded = proc do
|
178
|
-
begin
|
179
|
-
result = yield
|
180
|
-
promise.resolve(result)
|
181
|
-
rescue Exception => error
|
182
|
-
promise.reject(error)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
# Run the block after resolve if a block is passed in
|
187
|
-
if block
|
188
|
-
promise2 = promise.then do |val|
|
189
|
-
block.call(val)
|
190
|
-
end
|
191
|
-
else
|
192
|
-
promise2 = promise
|
193
|
-
end
|
194
|
-
|
195
175
|
if @model.loaded_state == :loaded
|
196
|
-
|
176
|
+
promise.resolve(nil)
|
197
177
|
else
|
198
178
|
proc do |comp|
|
199
179
|
if @model.loaded_state == :loaded
|
200
|
-
|
180
|
+
promise.resolve(nil)
|
201
181
|
|
202
182
|
comp.stop
|
203
183
|
end
|
204
184
|
end.watch!
|
205
185
|
end
|
206
186
|
|
207
|
-
|
187
|
+
promise
|
208
188
|
end
|
209
189
|
|
210
190
|
# Returns a promise that is resolved/rejected when the query is complete. Any
|
@@ -11,13 +11,16 @@ module Volt
|
|
11
11
|
@old_size = 0
|
12
12
|
end
|
13
13
|
|
14
|
+
def respond_to_missing?(method_name, include_private = false)
|
15
|
+
@array.respond_to?(method_name, include_private) || super
|
16
|
+
end
|
17
|
+
|
14
18
|
# Forward any missing methods to the array
|
15
19
|
def method_missing(method_name, *args, &block)
|
16
20
|
# Long term we should probably handle all Enum methods
|
17
21
|
# directly for smarter updating. For now, just depend on size
|
18
22
|
# so updates retrigger the whole method call.
|
19
23
|
@size_dep.depend
|
20
|
-
|
21
24
|
@array.send(method_name, *args, &block)
|
22
25
|
end
|
23
26
|
|
@@ -77,7 +77,7 @@ module Volt
|
|
77
77
|
connect_to_peers
|
78
78
|
end
|
79
79
|
else
|
80
|
-
Volt.logger.error('Unable to connect to the database. Volt will still run, but the message bus requires a database connection to setup connections between nodes, so the message bus has been disabled. This means updates will not be propagated between instances (server, console, runners, etc...)')
|
80
|
+
Volt.logger.error('Unable to connect to the database. Currently Volt requires running mongodb for a few things to work. Volt will still run, but the message bus requires a database connection to setup connections between nodes, so the message bus has been disabled. Also, the store collection can not be used without a database. This means updates will not be propagated between instances (server, console, runners, etc...)')
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
@@ -181,7 +181,7 @@ module Volt
|
|
181
181
|
def still_alive?(peer_server_id)
|
182
182
|
# Unable to write to the socket, retry until the instance is no
|
183
183
|
# longer marking its self as active in the database
|
184
|
-
peer_table = @volt_app.store.
|
184
|
+
peer_table = @volt_app.store.active_volt_instances
|
185
185
|
peer = peer_table.where(server_id: peer_server_id).first.sync
|
186
186
|
if peer
|
187
187
|
# Found the peer, retry if it has reported in in the last 2
|
@@ -33,7 +33,7 @@ module Volt
|
|
33
33
|
|
34
34
|
# Register this server as active with the database
|
35
35
|
def register
|
36
|
-
instances = @volt_app.store.
|
36
|
+
instances = @volt_app.store.active_volt_instances
|
37
37
|
instances.where(server_id: @server_id).first.then do |item|
|
38
38
|
ips = local_ips.join(',')
|
39
39
|
time = Time.now.to_i
|
@@ -50,10 +50,11 @@ module Volt
|
|
50
50
|
|
51
51
|
rack_app.use HttpResource, volt_app, volt_app.router
|
52
52
|
|
53
|
+
# serve assets from public
|
53
54
|
rack_app.use Rack::Static,
|
54
55
|
urls: ['/'],
|
55
|
-
root: '
|
56
|
-
index: '',
|
56
|
+
root: 'public',
|
57
|
+
index: 'index.html',
|
57
58
|
header_rules: [
|
58
59
|
[:all, { 'Cache-Control' => 'public, max-age=86400' }]
|
59
60
|
]
|
@@ -4,6 +4,16 @@ require 'uri'
|
|
4
4
|
# from the dependencies.rb files.
|
5
5
|
module Volt
|
6
6
|
class AssetFiles
|
7
|
+
def self.from_cache(component_name, component_paths)
|
8
|
+
# @cache ||= {}
|
9
|
+
|
10
|
+
# @cache[component_name] ||= begin
|
11
|
+
# not cached, create
|
12
|
+
|
13
|
+
self.new(component_name, component_paths)
|
14
|
+
# end
|
15
|
+
end
|
16
|
+
|
7
17
|
def initialize(component_name, component_paths)
|
8
18
|
@component_paths = component_paths
|
9
19
|
@assets = []
|
@@ -61,6 +71,14 @@ module Volt
|
|
61
71
|
end
|
62
72
|
end
|
63
73
|
|
74
|
+
# Called when you want to add a gem to the opal load path so it can be
|
75
|
+
# required on the client side.
|
76
|
+
def opal_gem(gem_name)
|
77
|
+
Opal.use_gem(gem_name)
|
78
|
+
Opal.paths.uniq!
|
79
|
+
# require(gem_name)
|
80
|
+
end
|
81
|
+
|
64
82
|
def components
|
65
83
|
@included_components.keys
|
66
84
|
end
|
@@ -17,7 +17,7 @@ module Volt
|
|
17
17
|
# Start with config code
|
18
18
|
code = @client ? generate_config_code : ''
|
19
19
|
|
20
|
-
asset_files = AssetFiles.
|
20
|
+
asset_files = AssetFiles.from_cache(@component_name, @component_paths)
|
21
21
|
asset_files.component_paths.each do |component_path, component_name|
|
22
22
|
code << ComponentTemplates.new(component_path, component_name, @client).code
|
23
23
|
code << "\n\n"
|
@@ -58,11 +58,11 @@ module Volt
|
|
58
58
|
|
59
59
|
def javascript_tags
|
60
60
|
# TODO: Cache somehow, this is being loaded every time
|
61
|
-
AssetFiles.
|
61
|
+
AssetFiles.from_cache('main', @component_paths).javascript_tags(@volt_app)
|
62
62
|
end
|
63
63
|
|
64
64
|
def css_tags
|
65
|
-
AssetFiles.
|
65
|
+
AssetFiles.from_cache('main', @component_paths).css_tags
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
data/lib/volt/spec/setup.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
# Require the original promise library first.
|
2
|
-
require 'volt/utils/promise'
|
3
|
-
|
4
1
|
# A temp patch for promises until https://github.com/opal/opal/pull/725 is released.
|
5
2
|
class Promise
|
6
3
|
class UnrealizedPromiseException < RuntimeError ; end
|
@@ -113,6 +110,10 @@ class Promise
|
|
113
110
|
end
|
114
111
|
|
115
112
|
if error
|
113
|
+
if error.is_a?(RSpec::Expectations::ExpectationNotMetError)
|
114
|
+
# re-raise
|
115
|
+
raise error
|
116
|
+
end
|
116
117
|
err_str = "Exception in Promise at .sync: #{error.inspect}"
|
117
118
|
err_str += error.backtrace.join("\n") if error.respond_to?(:backtrace)
|
118
119
|
Volt.logger.error(err_str)
|
data/lib/volt/version.rb
CHANGED
data/lib/volt/volt/app.rb
CHANGED
@@ -88,6 +88,11 @@ module Volt
|
|
88
88
|
|
89
89
|
load_app_code
|
90
90
|
|
91
|
+
# Load up the main component dependencies. This is needed to load in
|
92
|
+
# any opal_gem calls in dependencies.rb
|
93
|
+
# TODO: Needs to support all components
|
94
|
+
AssetFiles.from_cache('main', component_paths)
|
95
|
+
|
91
96
|
reset_query_pool!
|
92
97
|
|
93
98
|
# Setup the middleware that we can only setup after all components boot.
|
data/lib/volt/volt/users.rb
CHANGED
@@ -93,12 +93,12 @@ module Volt
|
|
93
93
|
|
94
94
|
# Put in a deprecation placeholder
|
95
95
|
def user
|
96
|
-
Volt.logger.warn('
|
96
|
+
Volt.logger.warn('Deprecation: Volt.user has been renamed to Volt.current_user (to be more clear about what it returns). Volt.user will be deprecated in the future.')
|
97
97
|
current_user
|
98
98
|
end
|
99
99
|
|
100
100
|
def fetch_current_user
|
101
|
-
Volt.logger.warn("
|
101
|
+
Volt.logger.warn("Deprecation Warning: fetch current user have been depricated, Volt.current_user returns a promise now.")
|
102
102
|
current_user
|
103
103
|
end
|
104
104
|
|
@@ -3,6 +3,7 @@ require 'volt/extra_core/array'
|
|
3
3
|
|
4
4
|
class TestClassAttributes
|
5
5
|
class_attribute :some_data
|
6
|
+
class_attribute :attr2
|
6
7
|
end
|
7
8
|
|
8
9
|
class TestSubClassAttributes < TestClassAttributes
|
@@ -30,4 +31,14 @@ describe 'extra_core class addons' do
|
|
30
31
|
expect(TestSubClassAttributes.some_data).to eq(10)
|
31
32
|
expect(TestSubClassAttributes2.some_data).to eq(15)
|
32
33
|
end
|
34
|
+
|
35
|
+
it 'should let you change a class attribute on the child without affecting the parent' do
|
36
|
+
TestClassAttributes.attr2 = 1
|
37
|
+
expect(TestSubClassAttributes.attr2).to eq(1)
|
38
|
+
|
39
|
+
TestSubClassAttributes.attr2 = 2
|
40
|
+
expect(TestClassAttributes.attr2).to eq(1)
|
41
|
+
expect(TestSubClassAttributes.attr2).to eq(2)
|
42
|
+
expect(TestSubClassAttributes2.attr2).to eq(1)
|
43
|
+
end
|
33
44
|
end
|
@@ -21,4 +21,20 @@ describe Volt::ArrayModel do
|
|
21
21
|
Volt::Computation.flush!
|
22
22
|
expect(count).to eq(2)
|
23
23
|
end
|
24
|
+
|
25
|
+
it 'should return the index of a model' do
|
26
|
+
array_model = Volt::ArrayModel.new([1,2,3])
|
27
|
+
|
28
|
+
expect(array_model.index(2)).to eq(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should flatten' do
|
32
|
+
array = Volt::ArrayModel.new([])
|
33
|
+
|
34
|
+
array << Volt::ArrayModel.new([Volt::ArrayModel.new([1,2]), Volt::ArrayModel.new([3])])
|
35
|
+
array << Volt::ArrayModel.new([Volt::ArrayModel.new([4,5]), Volt::ArrayModel.new([6])])
|
36
|
+
|
37
|
+
expect(array.flatten.size).to eq(6)
|
38
|
+
expect(array.to_a.flatten.size).to eq(6)
|
39
|
+
end
|
24
40
|
end
|
@@ -6,6 +6,9 @@ class ExampleModelWithField < Volt::Model
|
|
6
6
|
field :value, Numeric
|
7
7
|
end
|
8
8
|
|
9
|
+
class ExampleModelWithField2 < ExampleModelWithField
|
10
|
+
end
|
11
|
+
|
9
12
|
describe 'field helpers' do
|
10
13
|
let(:model) { ExampleModelWithField.new }
|
11
14
|
it 'should allow a user to setup a field that can be written to and read' do
|
@@ -23,7 +26,7 @@ describe 'field helpers' do
|
|
23
26
|
|
24
27
|
it 'should raise an error when an invalid cast type is provided' do
|
25
28
|
expect do
|
26
|
-
|
29
|
+
ExampleModelWithField2.field :awesome, Array
|
27
30
|
end.to raise_error(FieldHelpers::InvalidFieldClass)
|
28
31
|
end
|
29
32
|
|
@@ -42,4 +45,8 @@ describe 'field helpers' do
|
|
42
45
|
expect(error).to eq({})
|
43
46
|
end
|
44
47
|
end
|
48
|
+
|
49
|
+
it 'should track the fields on the model class' do
|
50
|
+
expect(ExampleModelWithField.fields_data).to eq({:name=>[nil, {}], :value=>[Numeric, {}]})
|
51
|
+
end
|
45
52
|
end
|
@@ -46,6 +46,17 @@ class ::TestUpdateReadCheck < Volt::Model
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
class ::TestPromisePermission < Volt::Model
|
50
|
+
attr_reader :called_deny
|
51
|
+
permissions(:create) do
|
52
|
+
$test_promise = Promise.new
|
53
|
+
$test_promise.then do
|
54
|
+
@called_deny = true
|
55
|
+
deny
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
49
60
|
describe 'model permissions' do
|
50
61
|
let(:user_todo) { TestUserTodo.new }
|
51
62
|
|
@@ -162,5 +173,19 @@ describe 'model permissions' do
|
|
162
173
|
|
163
174
|
expect(model.read_check).to eq(nil)
|
164
175
|
end
|
176
|
+
|
177
|
+
it 'should allow permission blocks to return a promise' do
|
178
|
+
promise = store._test_promise_permissions.create({})
|
179
|
+
|
180
|
+
expect(promise.resolved?).to eq(false)
|
181
|
+
expect(promise.rejected?).to eq(false)
|
182
|
+
$test_promise.resolve(nil)
|
183
|
+
|
184
|
+
expect(promise.resolved?).to eq(false)
|
185
|
+
expect(promise.rejected?).to eq(true)
|
186
|
+
# puts "#{promise.error.inspect}"
|
187
|
+
# puts promise.error.backtrace.join("\n")
|
188
|
+
expect(promise.error.to_s).to match(/permissions did not allow create for/)
|
189
|
+
end
|
165
190
|
end
|
166
191
|
end
|
data/volt.gemspec
CHANGED
@@ -44,7 +44,7 @@ Gem::Specification.new do |spec|
|
|
44
44
|
spec.add_development_dependency 'capybara', '~> 2.4.2'
|
45
45
|
|
46
46
|
# There is a big performance issue with selenium-webdriver on v2.45.0
|
47
|
-
spec.add_development_dependency 'selenium-webdriver', '~> 2.
|
47
|
+
spec.add_development_dependency 'selenium-webdriver', '~> 2.46.2'
|
48
48
|
spec.add_development_dependency 'chromedriver2-helper', '~> 0.0.8'
|
49
49
|
spec.add_development_dependency 'poltergeist', '~> 1.5.0'
|
50
50
|
spec.add_development_dependency 'thin', '~> 1.6.3'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: volt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.5.
|
4
|
+
version: 0.9.5.pre4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Stout
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -212,14 +212,14 @@ dependencies:
|
|
212
212
|
requirements:
|
213
213
|
- - "~>"
|
214
214
|
- !ruby/object:Gem::Version
|
215
|
-
version: 2.
|
215
|
+
version: 2.46.2
|
216
216
|
type: :development
|
217
217
|
prerelease: false
|
218
218
|
version_requirements: !ruby/object:Gem::Requirement
|
219
219
|
requirements:
|
220
220
|
- - "~>"
|
221
221
|
- !ruby/object:Gem::Version
|
222
|
-
version: 2.
|
222
|
+
version: 2.46.2
|
223
223
|
- !ruby/object:Gem::Dependency
|
224
224
|
name: chromedriver2-helper
|
225
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -374,6 +374,7 @@ files:
|
|
374
374
|
- app/volt/assets/js/volt_watch.js
|
375
375
|
- app/volt/config/dependencies.rb
|
376
376
|
- app/volt/controllers/notices_controller.rb
|
377
|
+
- app/volt/models/active_volt_instance.rb
|
377
378
|
- app/volt/models/user.rb
|
378
379
|
- app/volt/tasks/live_query/live_query.rb
|
379
380
|
- app/volt/tasks/live_query/live_query_pool.rb
|
@@ -572,7 +573,6 @@ files:
|
|
572
573
|
- lib/volt/utils/logging/task_logger.rb
|
573
574
|
- lib/volt/utils/modes.rb
|
574
575
|
- lib/volt/utils/parsing.rb
|
575
|
-
- lib/volt/utils/promise.rb
|
576
576
|
- lib/volt/utils/promise_extensions.rb
|
577
577
|
- lib/volt/utils/read_write_lock.rb
|
578
578
|
- lib/volt/utils/set_patch.rb
|
data/lib/volt/utils/promise.rb
DELETED
@@ -1,429 +0,0 @@
|
|
1
|
-
# A copy of the opal 0.8 promise library. The one in 0.7.x has some bugs.
|
2
|
-
|
3
|
-
# {Promise} is used to help structure asynchronous code.
|
4
|
-
#
|
5
|
-
# It is available in the Opal standard library, and can be required in any Opal
|
6
|
-
# application:
|
7
|
-
#
|
8
|
-
# require 'promise'
|
9
|
-
#
|
10
|
-
# ## Basic Usage
|
11
|
-
#
|
12
|
-
# Promises are created and returned as objects with the assumption that they
|
13
|
-
# will eventually be resolved or rejected, but never both. A {Promise} has
|
14
|
-
# a {#then} and {#fail} method (or one of their aliases) that can be used to
|
15
|
-
# register a block that gets called once resolved or rejected.
|
16
|
-
#
|
17
|
-
# promise = Promise.new
|
18
|
-
#
|
19
|
-
# promise.then {
|
20
|
-
# puts "resolved!"
|
21
|
-
# }.fail {
|
22
|
-
# puts "rejected!"
|
23
|
-
# }
|
24
|
-
#
|
25
|
-
# # some time later
|
26
|
-
# promise.resolve
|
27
|
-
#
|
28
|
-
# # => "resolved!"
|
29
|
-
#
|
30
|
-
# It is important to remember that a promise can only be resolved or rejected
|
31
|
-
# once, so the block will only ever be called once (or not at all).
|
32
|
-
#
|
33
|
-
# ## Resolving Promises
|
34
|
-
#
|
35
|
-
# To resolve a promise, means to inform the {Promise} that it has succeeded
|
36
|
-
# or evaluated to a useful value. {#resolve} can be passed a value which is
|
37
|
-
# then passed into the block handler:
|
38
|
-
#
|
39
|
-
# def get_json
|
40
|
-
# promise = Promise.new
|
41
|
-
#
|
42
|
-
# HTTP.get("some_url") do |req|
|
43
|
-
# promise.resolve req.json
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# promise
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# get_json.then do |json|
|
50
|
-
# puts "got some JSON from server"
|
51
|
-
# end
|
52
|
-
#
|
53
|
-
# ## Rejecting Promises
|
54
|
-
#
|
55
|
-
# Promises are also designed to handle error cases, or situations where an
|
56
|
-
# outcome is not as expected. Taking the previous example, we can also pass
|
57
|
-
# a value to a {#reject} call, which passes that object to the registered
|
58
|
-
# {#fail} handler:
|
59
|
-
#
|
60
|
-
# def get_json
|
61
|
-
# promise = Promise.new
|
62
|
-
#
|
63
|
-
# HTTP.get("some_url") do |req|
|
64
|
-
# if req.ok?
|
65
|
-
# promise.resolve req.json
|
66
|
-
# else
|
67
|
-
# promise.reject req
|
68
|
-
# end
|
69
|
-
#
|
70
|
-
# promise
|
71
|
-
# end
|
72
|
-
#
|
73
|
-
# get_json.then {
|
74
|
-
# # ...
|
75
|
-
# }.fail { |req|
|
76
|
-
# puts "it went wrong: #{req.message}"
|
77
|
-
# }
|
78
|
-
#
|
79
|
-
# ## Chaining Promises
|
80
|
-
#
|
81
|
-
# Promises become even more useful when chained together. Each {#then} or
|
82
|
-
# {#fail} call returns a new {Promise} which can be used to chain more and more
|
83
|
-
# handlers together.
|
84
|
-
#
|
85
|
-
# promise.then { wait_for_something }.then { do_something_else }
|
86
|
-
#
|
87
|
-
# Rejections are propagated through the entire chain, so a "catch all" handler
|
88
|
-
# can be attached at the end of the tail:
|
89
|
-
#
|
90
|
-
# promise.then { ... }.then { ... }.fail { ... }
|
91
|
-
#
|
92
|
-
# ## Composing Promises
|
93
|
-
#
|
94
|
-
# {Promise.when} can be used to wait for more than one promise to resolve (or
|
95
|
-
# reject). Using the previous example, we could request two different json
|
96
|
-
# requests and wait for both to finish:
|
97
|
-
#
|
98
|
-
# Promise.when(get_json, get_json2).then |first, second|
|
99
|
-
# puts "got two json payloads: #{first}, #{second}"
|
100
|
-
# end
|
101
|
-
#
|
102
|
-
class Promise
|
103
|
-
def self.value(value)
|
104
|
-
new.resolve(value)
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.error(value)
|
108
|
-
new.reject(value)
|
109
|
-
end
|
110
|
-
|
111
|
-
def self.when(*promises)
|
112
|
-
When.new(promises)
|
113
|
-
end
|
114
|
-
|
115
|
-
attr_reader :error, :prev, :next
|
116
|
-
|
117
|
-
def initialize(action = {})
|
118
|
-
@action = action
|
119
|
-
|
120
|
-
@realized = false
|
121
|
-
@exception = false
|
122
|
-
@value = nil
|
123
|
-
@error = nil
|
124
|
-
@delayed = false
|
125
|
-
|
126
|
-
@prev = nil
|
127
|
-
@next = nil
|
128
|
-
end
|
129
|
-
|
130
|
-
def value
|
131
|
-
if Promise === @value
|
132
|
-
@value.value
|
133
|
-
else
|
134
|
-
@value
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def act?
|
139
|
-
@action.has_key?(:success) || @action.has_key?(:always)
|
140
|
-
end
|
141
|
-
|
142
|
-
def action
|
143
|
-
@action.keys
|
144
|
-
end
|
145
|
-
|
146
|
-
def exception?
|
147
|
-
@exception
|
148
|
-
end
|
149
|
-
|
150
|
-
def realized?
|
151
|
-
!!@realized
|
152
|
-
end
|
153
|
-
|
154
|
-
def resolved?
|
155
|
-
@realized == :resolve
|
156
|
-
end
|
157
|
-
|
158
|
-
def rejected?
|
159
|
-
@realized == :reject
|
160
|
-
end
|
161
|
-
|
162
|
-
def ^(promise)
|
163
|
-
promise << self
|
164
|
-
self >> promise
|
165
|
-
|
166
|
-
promise
|
167
|
-
end
|
168
|
-
|
169
|
-
def <<(promise)
|
170
|
-
@prev = promise
|
171
|
-
|
172
|
-
self
|
173
|
-
end
|
174
|
-
|
175
|
-
def >>(promise)
|
176
|
-
@next = promise
|
177
|
-
|
178
|
-
if exception?
|
179
|
-
promise.reject(@delayed[0])
|
180
|
-
elsif resolved?
|
181
|
-
promise.resolve(@delayed ? @delayed[0] : value)
|
182
|
-
elsif rejected?
|
183
|
-
if !@action.has_key?(:failure) || Promise === (@delayed ? @delayed[0] : @error)
|
184
|
-
promise.reject(@delayed ? @delayed[0] : error)
|
185
|
-
elsif promise.action.include?(:always)
|
186
|
-
promise.reject(@delayed ? @delayed[0] : error)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
self
|
191
|
-
end
|
192
|
-
|
193
|
-
def resolve(value = nil)
|
194
|
-
if realized?
|
195
|
-
raise ArgumentError, 'the promise has already been realized'
|
196
|
-
end
|
197
|
-
|
198
|
-
if Promise === value
|
199
|
-
return (value << @prev) ^ self
|
200
|
-
end
|
201
|
-
|
202
|
-
begin
|
203
|
-
if block = @action[:success] || @action[:always]
|
204
|
-
value = block.call(value)
|
205
|
-
end
|
206
|
-
|
207
|
-
resolve!(value)
|
208
|
-
rescue Exception => e
|
209
|
-
exception!(e)
|
210
|
-
end
|
211
|
-
|
212
|
-
self
|
213
|
-
end
|
214
|
-
|
215
|
-
def resolve!(value)
|
216
|
-
@realized = :resolve
|
217
|
-
@value = value
|
218
|
-
|
219
|
-
if @next
|
220
|
-
@next.resolve(value)
|
221
|
-
else
|
222
|
-
@delayed = [value]
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def reject(value = nil)
|
227
|
-
if realized?
|
228
|
-
raise ArgumentError, 'the promise has already been realized'
|
229
|
-
end
|
230
|
-
|
231
|
-
if Promise === value
|
232
|
-
return (value << @prev) ^ self
|
233
|
-
end
|
234
|
-
|
235
|
-
begin
|
236
|
-
if block = @action[:failure] || @action[:always]
|
237
|
-
value = block.call(value)
|
238
|
-
end
|
239
|
-
|
240
|
-
if @action.has_key?(:always)
|
241
|
-
resolve!(value)
|
242
|
-
else
|
243
|
-
reject!(value)
|
244
|
-
end
|
245
|
-
rescue Exception => e
|
246
|
-
exception!(e)
|
247
|
-
end
|
248
|
-
|
249
|
-
self
|
250
|
-
end
|
251
|
-
|
252
|
-
def reject!(value)
|
253
|
-
@realized = :reject
|
254
|
-
@error = value
|
255
|
-
|
256
|
-
if @next
|
257
|
-
@next.reject(value)
|
258
|
-
else
|
259
|
-
@delayed = [value]
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def exception!(error)
|
264
|
-
@exception = true
|
265
|
-
|
266
|
-
reject!(error)
|
267
|
-
end
|
268
|
-
|
269
|
-
def then(&block)
|
270
|
-
if @next
|
271
|
-
raise ArgumentError, 'a promise has already been chained'
|
272
|
-
end
|
273
|
-
|
274
|
-
self ^ Promise.new(success: block)
|
275
|
-
end
|
276
|
-
|
277
|
-
alias do then
|
278
|
-
|
279
|
-
def fail(&block)
|
280
|
-
if @next
|
281
|
-
raise ArgumentError, 'a promise has already been chained'
|
282
|
-
end
|
283
|
-
|
284
|
-
self ^ Promise.new(failure: block)
|
285
|
-
end
|
286
|
-
|
287
|
-
alias rescue fail
|
288
|
-
alias catch fail
|
289
|
-
|
290
|
-
def always(&block)
|
291
|
-
if @next
|
292
|
-
raise ArgumentError, 'a promise has already been chained'
|
293
|
-
end
|
294
|
-
|
295
|
-
self ^ Promise.new(always: block)
|
296
|
-
end
|
297
|
-
|
298
|
-
alias finally always
|
299
|
-
alias ensure always
|
300
|
-
|
301
|
-
def trace(depth = nil, &block)
|
302
|
-
if @next
|
303
|
-
raise ArgumentError, 'a promise has already been chained'
|
304
|
-
end
|
305
|
-
|
306
|
-
self ^ Trace.new(depth, block)
|
307
|
-
end
|
308
|
-
|
309
|
-
def inspect
|
310
|
-
result = "#<#{self.class}(#{object_id})"
|
311
|
-
|
312
|
-
if @next
|
313
|
-
result += " >> #{@next.inspect}"
|
314
|
-
end
|
315
|
-
|
316
|
-
if realized?
|
317
|
-
result += ": #{(@value || @error).inspect}>"
|
318
|
-
else
|
319
|
-
result += ">"
|
320
|
-
end
|
321
|
-
|
322
|
-
result
|
323
|
-
end
|
324
|
-
|
325
|
-
class Trace < self
|
326
|
-
def self.it(promise)
|
327
|
-
current = []
|
328
|
-
|
329
|
-
if promise.act? || promise.prev.nil?
|
330
|
-
current.push(promise.value)
|
331
|
-
end
|
332
|
-
|
333
|
-
if prev = promise.prev
|
334
|
-
current.concat(it(prev))
|
335
|
-
else
|
336
|
-
current
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
def initialize(depth, block)
|
341
|
-
@depth = depth
|
342
|
-
|
343
|
-
super success: -> {
|
344
|
-
trace = Trace.it(self).reverse
|
345
|
-
trace.pop
|
346
|
-
|
347
|
-
if depth && depth <= trace.length
|
348
|
-
trace.shift(trace.length - depth)
|
349
|
-
end
|
350
|
-
|
351
|
-
block.call(*trace)
|
352
|
-
}
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
class When < self
|
357
|
-
def initialize(promises = [])
|
358
|
-
super()
|
359
|
-
|
360
|
-
@wait = []
|
361
|
-
|
362
|
-
promises.each {|promise|
|
363
|
-
wait promise
|
364
|
-
}
|
365
|
-
end
|
366
|
-
|
367
|
-
def each(&block)
|
368
|
-
raise ArgumentError, 'no block given' unless block
|
369
|
-
|
370
|
-
self.then {|values|
|
371
|
-
values.each(&block)
|
372
|
-
}
|
373
|
-
end
|
374
|
-
|
375
|
-
def collect(&block)
|
376
|
-
raise ArgumentError, 'no block given' unless block
|
377
|
-
|
378
|
-
self.then {|values|
|
379
|
-
When.new(values.map(&block))
|
380
|
-
}
|
381
|
-
end
|
382
|
-
|
383
|
-
def inject(*args, &block)
|
384
|
-
self.then {|values|
|
385
|
-
values.reduce(*args, &block)
|
386
|
-
}
|
387
|
-
end
|
388
|
-
|
389
|
-
alias map collect
|
390
|
-
|
391
|
-
alias reduce inject
|
392
|
-
|
393
|
-
def wait(promise)
|
394
|
-
unless Promise === promise
|
395
|
-
promise = Promise.value(promise)
|
396
|
-
end
|
397
|
-
|
398
|
-
if promise.act?
|
399
|
-
promise = promise.then
|
400
|
-
end
|
401
|
-
|
402
|
-
@wait << promise
|
403
|
-
|
404
|
-
promise.always {
|
405
|
-
try if @next
|
406
|
-
}
|
407
|
-
|
408
|
-
self
|
409
|
-
end
|
410
|
-
|
411
|
-
alias and wait
|
412
|
-
|
413
|
-
def >>(*)
|
414
|
-
super.tap {
|
415
|
-
try
|
416
|
-
}
|
417
|
-
end
|
418
|
-
|
419
|
-
def try
|
420
|
-
if @wait.all?(&:realized?)
|
421
|
-
if promise = @wait.find(&:rejected?)
|
422
|
-
reject(promise.error)
|
423
|
-
else
|
424
|
-
resolve(@wait.map(&:value))
|
425
|
-
end
|
426
|
-
end
|
427
|
-
end
|
428
|
-
end
|
429
|
-
end
|