nodo 1.6.0 → 1.6.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 421dba598b565369d200c90c8b095786f7b0a98de7c97e88c6927b59b64591a0
4
- data.tar.gz: c6ba40e8873c90713edf4cc0887dcf64c3e7b9ea4629f4d97e3fc3c8c58218b9
3
+ metadata.gz: a4870340520e40bf14cf0e9261711983d5332eaf04419ff1281626ed5b4716e1
4
+ data.tar.gz: 162b5e4f6739728c91f64422acb579db9c004429f92402db5f393cfb76ffa675
5
5
  SHA512:
6
- metadata.gz: 58410d1f53c78f40314ae24ea5f6e7d2aeca99f37aeeb6495ccdf0ee8a1fe903c956ad23462cba48ad38132df5614b3f354ee6c09638f52d47fe78e1e90d3064
7
- data.tar.gz: 8374444cbbc7257b206fb17abd3470df3488885ae843e414bca3e645cf6c9f9e9d91d6de6f354e5f7fe4a1a98d780f04562df89752286851f8727d86e3c88aa9
6
+ metadata.gz: dbe6fb8dce3f39968b26800a28f50b7d2c1ac49ffc60537a50950af598bc329b6ae59fae2db8285b16167238448ef370502d7b6f736c4dc4e66563ae0050ccfd
7
+ data.tar.gz: 37dd504e71489566a9b57df0e217c2f25d09ce3d511dab2ac6daf849829b90f1293ed07dd4e320eb70ef573da7b466263c704bb99cf46cb555449a8253909089
data/README.md CHANGED
@@ -91,6 +91,23 @@ class FooBar < Nodo::Core
91
91
  end
92
92
  ```
93
93
 
94
+ ### Dynamic ESM imports
95
+
96
+ ES modules can be imported dynamically using `nodo.import()`:
97
+
98
+ ```ruby
99
+ class DynamicFoo < Nodo::Core
100
+ function :v4, <<~JS
101
+ async () => {
102
+ const uuid = await nodo.import('uuid');
103
+ return await uuid.v4()
104
+ }
105
+ JS
106
+ end
107
+ ```
108
+
109
+ Note that the availability of dynamic imports depends on your Node version.
110
+
94
111
  ### Alternate function definition syntax
95
112
 
96
113
  JS code can also be supplied using the `code:` keyword argument:
@@ -131,6 +148,23 @@ class BarFoo < Nodo::Core
131
148
  end
132
149
  ```
133
150
 
151
+ With the above syntax, the script code will be generated during class definition
152
+ time. In order to have the code generated when the first instance is created, the
153
+ code can be defined inside a block:
154
+
155
+ ```ruby
156
+ class Foo < Nodo::Core
157
+ script do
158
+ <<~JS
159
+ var definitionTime = #{Time.now.to_json};
160
+ JS
161
+ end
162
+ end
163
+ ```
164
+
165
+ Note that the script will still be executed only once, when the first instance
166
+ of class is created.
167
+
134
168
  ### Inheritance
135
169
 
136
170
  Subclasses will inherit functions, constants, dependencies and scripts from
@@ -168,10 +202,50 @@ class SyncFoo < Nodo::Core
168
202
  end
169
203
  ```
170
204
 
205
+ ### Deferred function definition
206
+
207
+ By default, the function code string literal is created when the class
208
+ is defined. Therefore any string interpolation inside the code will take
209
+ place at definition time.
210
+
211
+ In order to defer the code generation until the first object instantiation,
212
+ the function code can be given inside a block:
213
+
214
+ ```ruby
215
+ class Deferred < Nodo::Core
216
+ function :now, <<~JS
217
+ () => { return #{Time.now.to_json}; }
218
+ JS
219
+
220
+ function :later do
221
+ <<~JS
222
+ () => { return #{Time.now.to_json}; }
223
+ JS
224
+ end
225
+ end
226
+
227
+ instance = Deferred.new
228
+ sleep 5
229
+ instance.now => "2021-10-28 20:30:00 +0200"
230
+ instance.later => "2021-10-28 20:30:05 +0200"
231
+ ```
232
+
233
+ The block will be invoked when the first instance is created. As with deferred
234
+ scripts, it will only be invoked once.
235
+
171
236
  ### Limiting function execution time
172
237
 
173
- The default timeout for a single JS function call is 60 seconds due to the
174
- `Net::HTTP` default. It can be overridden on a per-function basis:
238
+ The default timeout for a single JS function call is 60 seconds and can be
239
+ set globally:
240
+
241
+ ```ruby
242
+ Nodo.timeout = 5
243
+ ```
244
+
245
+ If the execution of a single function call exceeds the timeout, `Nodo::TimeoutError`
246
+ is raised.
247
+
248
+ The timeout can also be set on a per-function basis:
175
249
 
176
250
  ```ruby
177
251
  class Foo < Nodo::Core
@@ -208,8 +282,63 @@ Nodo.debug = true
208
282
  before instantiating any worker instances. The debug mode will be active during
209
283
  the current process run.
210
284
 
285
+ To print a debug message from JS code:
286
+
287
+ ```js
288
+ nodo.debug("Debug message");
289
+ ```
290
+
291
+ ### Evaluation
292
+
293
+ While `Nodo` is mainly function-based, it is possible to evaluate JS code in the
294
+ context of the defined object.
295
+
296
+ ```ruby
297
+ foo = Foo.new.evaluate("3 + 5")
298
+ => 8
299
+ ```
300
+
301
+ Evaluated code can access functions, required dependencies and constants:
302
+
303
+ ```ruby
304
+ class Foo < Nodo::Core
305
+ const :BAR, 'bar'
306
+ require :uuid
307
+ function :hello, code: '() => "world"'
308
+ end
309
+
310
+ foo = Foo.new
311
+
312
+ foo.evaluate('BAR')
313
+ => "bar"
314
+
315
+ foo.evaluate('uuid.v4()')
316
+ => "f258bef3-0d6f-4566-ad39-d8dec973ef6b"
317
+
318
+ foo.evaluate('hello()')
319
+ => "world"
320
+ ```
321
+
322
+ Variables defined by evaluation are local to the current instance:
323
+
324
+ ```ruby
325
+ one = Foo.new
326
+ one.evaluate('a = 1')
327
+ two = Foo.new
328
+ two.evaluate('a = 2')
329
+ one.evaluate('a') => 1
330
+ two.evaluate('a') => 2
331
+ ```
332
+
333
+ ⚠️ Evaluation comes with the usual caveats:
334
+
335
+ - Avoid modifying any of your predefined identifiers. Remember that in JS,
336
+ as in Ruby, constants are not necessarily constant.
337
+ - Never evaluate any code which includes un-checked user data. The Node.js process
338
+ has full read/write access to your filesystem! 💥
339
+
211
340
 
212
- #### Clean your Rails root
341
+ ## Clean your Rails root
213
342
 
214
343
  For Rails applications, Nodo enables you to move `node_modules`, `package.json` and
215
344
  `yarn.lock` into your application's `vendor` folder by setting the `NODE_PATH` in
@@ -220,7 +349,7 @@ an initializer:
220
349
  Nodo.modules_root = Rails.root.join('vendor', 'node_modules')
221
350
  ```
222
351
 
223
- The rationale behind this is NPM modules being external vendor dependencies, which
352
+ The rationale for this is NPM modules being external vendor dependencies, which
224
353
  should not clutter the application root directory.
225
354
 
226
355
  With this new default, all `yarn` operations should be done after `cd`ing to `vendor`.
data/lib/nodo/constant.rb CHANGED
@@ -7,7 +7,7 @@ module Nodo
7
7
  end
8
8
 
9
9
  def to_js
10
- "const #{name} = #{value.to_json};\n"
10
+ "const #{name} = __nodo_klass__.#{name} = #{value.to_json};\n"
11
11
  end
12
12
  end
13
13
  end
data/lib/nodo/core.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  module Nodo
2
2
  class Core
3
3
  SOCKET_NAME = 'nodo.sock'
4
- DEFINE_METHOD = '__nodo_define_class__'
5
- TIMEOUT = 5
4
+ DEFINE_METHOD = '__nodo_define_class__'.freeze
5
+ EVALUATE_METHOD = '__nodo_evaluate__'.freeze
6
+ GC_METHOD = '__nodo_gc__'.freeze
7
+ INTERNAL_METHODS = [DEFINE_METHOD, EVALUATE_METHOD, GC_METHOD].freeze
8
+ LAUNCH_TIMEOUT = 5
6
9
  ARRAY_CLASS_ATTRIBUTES = %i[dependencies constants scripts].freeze
7
10
  HASH_CLASS_ATTRIBUTES = %i[functions].freeze
8
11
  CLASS_ATTRIBUTES = (ARRAY_CLASS_ATTRIBUTES + HASH_CLASS_ATTRIBUTES).freeze
@@ -10,6 +13,7 @@ module Nodo
10
13
  @@node_pid = nil
11
14
  @@tmpdir = nil
12
15
  @@mutex = Mutex.new
16
+ @@exiting = nil
13
17
 
14
18
  class << self
15
19
  extend Forwardable
@@ -21,15 +25,11 @@ module Nodo
21
25
  subclass.send "#{attr}=", send(attr).dup
22
26
  end
23
27
  end
24
-
28
+
25
29
  def instance
26
30
  @instance ||= new
27
31
  end
28
32
 
29
- def class_function(*methods)
30
- singleton_class.def_delegators(:instance, *methods)
31
- end
32
-
33
33
  def class_defined?
34
34
  !!class_defined
35
35
  end
@@ -42,6 +42,7 @@ module Nodo
42
42
  define_method "#{attr}=" do |value|
43
43
  instance_variable_set :"@#{attr}", value
44
44
  end
45
+ protected "#{attr}="
45
46
  end
46
47
 
47
48
  ARRAY_CLASS_ATTRIBUTES.each do |attr|
@@ -55,30 +56,6 @@ module Nodo
55
56
  instance_variable_get(:"@#{attr}") || instance_variable_set(:"@#{attr}", {})
56
57
  end
57
58
  end
58
-
59
- def require(*mods)
60
- deps = mods.last.is_a?(Hash) ? mods.pop : {}
61
- mods = mods.map { |m| [m, m] }.to_h
62
- self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package) }
63
- end
64
-
65
- def function(name, _code = nil, timeout: 60, code: nil)
66
- raise ArgumentError, "reserved method name #{name.inspect}" if Nodo::Core.method_defined?(name) || name.to_s == DEFINE_METHOD
67
- code = (code ||= _code).strip
68
- raise ArgumentError, 'function code is required' if '' == code
69
- loc = caller_locations(1, 1)[0]
70
- source_location = "#{loc.path}:#{loc.lineno}: in `#{name}'"
71
- self.functions = functions.merge(name => Function.new(name, _code || code, source_location, timeout))
72
- define_method(name) { |*args| call_js_method(name, args) }
73
- end
74
-
75
- def const(name, value)
76
- self.constants = constants + [Constant.new(name, value)]
77
- end
78
-
79
- def script(code)
80
- self.scripts = scripts + [Script.new(code)]
81
- end
82
59
 
83
60
  def generate_core_code
84
61
  <<~JS
@@ -106,8 +83,7 @@ module Nodo
106
83
  def generate_class_code
107
84
  <<~JS
108
85
  (() => {
109
- const __nodo_log = nodo.log;
110
- const __nodo_klass__ = {};
86
+ const __nodo_klass__ = { nodo: global.nodo };
111
87
  #{dependencies.map(&:to_js).join}
112
88
  #{constants.map(&:to_js).join}
113
89
  #{functions.values.map(&:to_js).join}
@@ -117,14 +93,55 @@ module Nodo
117
93
  JS
118
94
  end
119
95
 
96
+ protected
97
+
98
+ def finalize_context(context_id)
99
+ proc do
100
+ if not @@exiting and core = Nodo::Core.instance
101
+ core.send(:call_js_method, GC_METHOD, context_id)
102
+ end
103
+ end
104
+ end
105
+
120
106
  private
121
107
 
108
+ def require(*mods)
109
+ deps = mods.last.is_a?(Hash) ? mods.pop : {}
110
+ mods = mods.map { |m| [m, m] }.to_h
111
+ self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package) }
112
+ end
113
+
114
+ def function(name, _code = nil, timeout: Nodo.timeout, code: nil, &block)
115
+ raise ArgumentError, "reserved method name #{name.inspect}" if reserved_method_name?(name)
116
+ loc = caller_locations(1, 1)[0]
117
+ source_location = "#{loc.path}:#{loc.lineno}: in `#{name}'"
118
+ self.functions = functions.merge(name => Function.new(name, _code || code, source_location, timeout, &block))
119
+ define_method(name) { |*args| call_js_method(name, args) }
120
+ end
121
+
122
+ def class_function(*methods)
123
+ singleton_class.def_delegators(:instance, *methods)
124
+ end
125
+
126
+ def const(name, value)
127
+ self.constants = constants + [Constant.new(name, value)]
128
+ end
129
+
130
+ def script(code = nil, &block)
131
+ self.scripts = scripts + [Script.new(code, &block)]
132
+ end
133
+
122
134
  def nodo_js
123
135
  Pathname.new(__FILE__).dirname.join('nodo.js').to_s.to_json
124
136
  end
137
+
138
+ def reserved_method_name?(name)
139
+ Nodo::Core.method_defined?(name, false) || Nodo::Core.private_method_defined?(name, false) || name.to_s == DEFINE_METHOD
140
+ end
125
141
  end
126
142
 
127
143
  def initialize
144
+ raise ClassError, :new if self.class == Nodo::Core
128
145
  @@mutex.synchronize do
129
146
  ensure_process_is_spawned
130
147
  wait_for_socket
@@ -132,6 +149,13 @@ module Nodo
132
149
  end
133
150
  end
134
151
 
152
+ def evaluate(code)
153
+ ensure_context_is_defined
154
+ call_js_method(EVALUATE_METHOD, code)
155
+ end
156
+
157
+ private
158
+
135
159
  def node_pid
136
160
  @@node_pid
137
161
  end
@@ -148,9 +172,15 @@ module Nodo
148
172
  self.class.clsid
149
173
  end
150
174
 
175
+ def context_defined?
176
+ @context_defined
177
+ end
178
+
151
179
  def log_exception(e)
152
180
  return unless logger = Nodo.logger
153
- logger.error "\n#{e.class} (#{e.message}):\n\n#{e.backtrace.join("\n")}"
181
+ message = "\n#{e.class} (#{e.message})"
182
+ message << ":\n\n#{e.backtrace.join("\n")}" if e.backtrace
183
+ logger.error message
154
184
  end
155
185
 
156
186
  def ensure_process_is_spawned
@@ -164,12 +194,22 @@ module Nodo
164
194
  self.class.class_defined = true
165
195
  end
166
196
 
197
+ def ensure_context_is_defined
198
+ return if context_defined?
199
+ @@mutex.synchronize do
200
+ call_js_method(EVALUATE_METHOD, '')
201
+ ObjectSpace.define_finalizer(self, self.class.send(:finalize_context, self.object_id))
202
+ @context_defined = true
203
+ end
204
+ end
205
+
167
206
  def spawn_process
168
207
  @@tmpdir = Pathname.new(Dir.mktmpdir('nodo'))
169
208
  env = Nodo.env.merge('NODE_PATH' => Nodo.modules_root.to_s)
170
209
  env['NODO_DEBUG'] = '1' if Nodo.debug
171
210
  @@node_pid = Process.spawn(env, Nodo.binary, '-e', self.class.generate_core_code, '--', socket_path.to_s, err: :out)
172
211
  at_exit do
212
+ @@exiting = true
173
213
  Process.kill(:SIGTERM, node_pid) rescue Errno::ECHILD
174
214
  Process.wait(node_pid) rescue Errno::ECHILD
175
215
  FileUtils.remove_entry(tmpdir) if File.directory?(tmpdir)
@@ -179,7 +219,7 @@ module Nodo
179
219
  def wait_for_socket
180
220
  start = Time.now
181
221
  socket = nil
182
- while Time.now - start < TIMEOUT
222
+ while Time.now - start < LAUNCH_TIMEOUT
183
223
  begin
184
224
  break if socket = UNIXSocket.new(socket_path)
185
225
  rescue Errno::ENOENT, Errno::ECONNREFUSED, Errno::ENOTDIR
@@ -192,13 +232,19 @@ module Nodo
192
232
 
193
233
  def call_js_method(method, args)
194
234
  raise CallError, 'Node process not ready' unless node_pid
195
- raise CallError, "Class #{clsid} not defined" unless self.class.class_defined? || method == DEFINE_METHOD
235
+ raise CallError, "Class #{clsid} not defined" unless self.class.class_defined? || INTERNAL_METHODS.include?(method)
196
236
  function = self.class.functions[method]
197
- raise NameError, "undefined function `#{method}' for #{self.class}" unless function || method == DEFINE_METHOD
198
- request = Net::HTTP::Post.new("/#{clsid}/#{method}", 'Content-Type': 'application/json')
237
+ raise NameError, "undefined function `#{method}' for #{self.class}" unless function || INTERNAL_METHODS.include?(method)
238
+ context_id = case method
239
+ when DEFINE_METHOD then 0
240
+ when GC_METHOD then args.first
241
+ else
242
+ object_id
243
+ end
244
+ request = Net::HTTP::Post.new("/#{clsid}/#{context_id}/#{method}", 'Content-Type': 'application/json')
199
245
  request.body = JSON.dump(args)
200
246
  client = Client.new("unix://#{socket_path}")
201
- client.read_timeout = function.timeout if function
247
+ client.read_timeout = function&.timeout || Nodo.timeout
202
248
  response = client.request(request)
203
249
  if response.is_a?(Net::HTTPOK)
204
250
  parse_response(response)
@@ -215,7 +261,10 @@ module Nodo
215
261
  def handle_error(response, function)
216
262
  if response.body
217
263
  result = parse_response(response)
218
- error = JavaScriptError.new(result['error'], function) if result.is_a?(Hash) && result.key?('error')
264
+ error = if result.is_a?(Hash) && result['error'].is_a?(Hash)
265
+ attrs = result['error']
266
+ (attrs['nodo_dependency'] ? DependencyError : JavaScriptError).new(attrs, function)
267
+ end
219
268
  end
220
269
  error ||= CallError.new("Node returned #{response.code}")
221
270
  log_exception(error)
@@ -7,7 +7,16 @@ module Nodo
7
7
  end
8
8
 
9
9
  def to_js
10
- "const #{name} = require(#{package.to_json});\n"
10
+ <<~JS
11
+ const #{name} = __nodo_klass__.#{name} = (() => {
12
+ try {
13
+ return require(#{package.to_json});
14
+ } catch(e) {
15
+ e.nodo_dependency = #{package.to_json};
16
+ throw e;
17
+ }
18
+ })();
19
+ JS
11
20
  end
12
21
  end
13
22
  end
data/lib/nodo/errors.rb CHANGED
@@ -2,6 +2,11 @@ module Nodo
2
2
  class Error < StandardError; end
3
3
  class TimeoutError < Error; end
4
4
  class CallError < Error; end
5
+ class ClassError < Error
6
+ def initialize(method = nil)
7
+ super("Cannot call method `#{method}' on Nodo::Core, use subclass instead")
8
+ end
9
+ end
5
10
 
6
11
  class JavaScriptError < Error
7
12
  attr_reader :attributes
@@ -37,13 +42,23 @@ module Nodo
37
42
  end
38
43
 
39
44
  def generate_message
40
- message = "#{attributes['message'] || 'Unknown error'}"
41
- if loc = attributes['loc']
42
- message << loc.inject(' in') { |s, (key, value)| s << " #{key}: #{value}" }
43
- end
44
- message
45
+ message = "#{attributes['message'] || attributes['name'] || 'Unknown error'}"
46
+ message << format_location(attributes['loc'])
47
+ end
48
+
49
+ def format_location(loc)
50
+ return '' unless loc
51
+ loc.inject(' in') { |s, (key, value)| s << " #{key}: #{value}" }
45
52
  end
46
53
  end
47
54
 
48
- class DependencyError < JavaScriptError; end
55
+ class DependencyError < JavaScriptError
56
+ private
57
+
58
+ def generate_message
59
+ message = "#{attributes['message'] || attributes['name'] || 'Dependency error'}\n"
60
+ message << "The specified dependency '#{attributes['nodo_dependency']}' could not be loaded. "
61
+ message << "Run 'yarn add #{attributes['nodo_dependency']}' to install it.\n"
62
+ end
63
+ end
49
64
  end
data/lib/nodo/function.rb CHANGED
@@ -2,12 +2,17 @@ module Nodo
2
2
  class Function
3
3
  attr_reader :name, :code, :source_location, :timeout
4
4
 
5
- def initialize(name, code, source_location, timeout)
6
- @name, @code, @source_location, @timeout = name, code, source_location, timeout
5
+ def initialize(name, code, source_location, timeout, &block)
6
+ raise ArgumentError, 'cannot give code when block is given' if code && block
7
+ code = code.strip if code
8
+ raise ArgumentError, 'function code is required' if '' == code
9
+ raise ArgumentError, 'code is required' unless code || block
10
+ @name, @code, @source_location, @timeout = name, code || block, source_location, timeout
7
11
  end
8
12
 
9
13
  def to_js
10
- "const #{name} = __nodo_klass__.#{name} = (#{code});\n"
14
+ js = code.respond_to?(:call) ? code.call.strip : code
15
+ "const #{name} = __nodo_klass__.#{name} = (#{js});\n"
11
16
  end
12
17
  end
13
18
  end
data/lib/nodo/nodo.js CHANGED
@@ -1,5 +1,7 @@
1
1
  module.exports = (function() {
2
2
  const DEFINE_METHOD = '__nodo_define_class__';
3
+ const EVALUATE_METHOD = '__nodo_evaluate__';
4
+ const GC_METHOD = '__nodo_gc__';
3
5
  const DEBUG = process.env.NODO_DEBUG;
4
6
 
5
7
  const vm = require('vm');
@@ -7,9 +9,10 @@ module.exports = (function() {
7
9
  const path = require('path');
8
10
  const fs = require('fs');
9
11
  const performance = require('perf_hooks').performance;
10
-
12
+
11
13
  let server, closing;
12
14
  const classes = {};
15
+ const contexts = {};
13
16
 
14
17
  function render_error(e) {
15
18
  let errInfo = {};
@@ -45,7 +48,11 @@ module.exports = (function() {
45
48
  console.log(`[Nodo] ${message}`);
46
49
  }
47
50
  }
48
-
51
+
52
+ async function import_module(specifier) {
53
+ return await import(specifier);
54
+ }
55
+
49
56
  const core = {
50
57
  run: (socket) => {
51
58
  debug('Starting up...');
@@ -60,12 +67,17 @@ module.exports = (function() {
60
67
  }
61
68
 
62
69
  const url = req.url.substring(1);
63
- const [class_name, method] = url.split('/');
64
- let klass;
70
+ const [class_name, object_id, method] = url.split('/');
71
+ let klass, context;
65
72
 
66
73
  if (classes.hasOwnProperty(class_name)) {
67
74
  klass = classes[class_name];
68
- if (!klass.hasOwnProperty(method)) {
75
+ if (EVALUATE_METHOD == method) {
76
+ if (!contexts.hasOwnProperty(object_id)) {
77
+ contexts[object_id] = vm.createContext({ require: require, ...klass });
78
+ }
79
+ context = contexts[object_id];
80
+ } else if (!klass.hasOwnProperty(method) && !GC_METHOD == method) {
69
81
  return respond_with_error(res, 404, `Method ${class_name}#${method} not found`);
70
82
  }
71
83
  } else if (DEFINE_METHOD != method) {
@@ -87,13 +99,21 @@ module.exports = (function() {
87
99
 
88
100
  try {
89
101
  if (DEFINE_METHOD == method) {
90
- let new_class = vm.runInThisContext(input, class_name);
91
- classes[class_name] = new_class;
102
+ classes[class_name] = vm.runInThisContext(input, class_name);
92
103
  respond_with_data(res, class_name, start);
104
+ } else if (EVALUATE_METHOD == method) {
105
+ Promise.resolve(vm.runInNewContext(input, context)).then((result) => {
106
+ respond_with_data(res, result, start);
107
+ }).catch((error) => {
108
+ respond_with_error(res, 500, error);
109
+ });
110
+ } else if (GC_METHOD == method) {
111
+ delete contexts[object_id];
112
+ respond_with_data(res, true, start);
93
113
  } else {
94
- Promise.resolve(klass[method].apply(null, input)).then(function (result) {
114
+ Promise.resolve(klass[method].apply(null, input)).then((result) => {
95
115
  respond_with_data(res, result, start);
96
- }).catch(function(error) {
116
+ }).catch((error) => {
97
117
  respond_with_error(res, 500, error);
98
118
  });
99
119
  }
@@ -119,5 +139,5 @@ module.exports = (function() {
119
139
  }
120
140
  };
121
141
 
122
- return { core: core, debug: debug };
142
+ return { core: core, debug: debug, import: import_module };
123
143
  })();
data/lib/nodo/script.rb CHANGED
@@ -2,12 +2,14 @@ module Nodo
2
2
  class Script
3
3
  attr_reader :code
4
4
 
5
- def initialize(code)
6
- @code = code
5
+ def initialize(code = nil, &block)
6
+ raise ArgumentError, 'cannot give code when block is given' if code && block
7
+ @code = code || block
7
8
  end
8
9
 
9
10
  def to_js
10
- "#{code}\n"
11
+ js = code.respond_to?(:call) ? code.call : code
12
+ "#{js}\n"
11
13
  end
12
14
  end
13
15
  end
data/lib/nodo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Nodo
2
- VERSION = '1.6.0'
2
+ VERSION = '1.6.4'
3
3
  end
data/lib/nodo.rb CHANGED
@@ -9,13 +9,14 @@ require 'forwardable'
9
9
 
10
10
  module Nodo
11
11
  class << self
12
- attr_accessor :modules_root, :env, :binary, :logger, :debug
12
+ attr_accessor :modules_root, :env, :binary, :logger, :debug, :timeout
13
13
  end
14
14
  self.modules_root = './node_modules'
15
15
  self.env = {}
16
16
  self.binary = 'node'
17
17
  self.logger = Logger.new(STDOUT)
18
18
  self.debug = false
19
+ self.timeout = 60
19
20
  end
20
21
 
21
22
  require_relative 'nodo/version'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nodo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthias Grosser