Module: Cloudflare::Scheduled

Defined in:
lib/cloudflare_workers/scheduled.rb

Overview

Dispatcher singleton. The Rack handler installs the JS hook on boot; user code never calls anything in this module directly.

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.appObject

Override the dispatch target. By default the dispatcher uses ‘Rack::Handler::CloudflareWorkers.app`, which is whatever the user passed to top-level `run app`. Tests use this to plug a fake Sinatra subclass without booting the full handler.



97
98
99
# File 'lib/cloudflare_workers/scheduled.rb', line 97

def app
  @app
end

Class Method Details

.await_promise(promise) ⇒ Object

Await a JS Promise from inside ‘# await: true` code without exposing the `__await__` keyword to callers. Re-throws Promise rejections as synchronous Ruby exceptions so callers can rescue. Sinatra::Scheduled uses this to bridge per-job exceptions out of the async block boundary.

Implemented as ‘__await__` directly so Opal compiles this method itself as async — callers in `# await: true` files that do `await_promise(p)` get the resolved value back the same way they would from a bare `p.__await__`. Rejections re-throw as Ruby exceptions thanks to ES8 `await`’s built-in throw semantics.



151
152
153
# File 'lib/cloudflare_workers/scheduled.rb', line 151

def self.await_promise(promise)
  promise.__await__
end

.dispatch(cron, scheduled_time = Time.now, js_env = nil, js_ctx = nil) ⇒ Object

Test-friendly direct entry point. Lets ‘test/scheduled_smoke.rb` exercise the same dispatch logic without going through the JS hook. `cron` / `scheduled_time` are plain Ruby values. Returns the awaited result Hash (callers can still `__await__` the outer Promise — this method is async since it uses `__await__`).



133
134
135
136
137
138
# File 'lib/cloudflare_workers/scheduled.rb', line 133

def self.dispatch(cron, scheduled_time = Time.now, js_env = nil, js_ctx = nil)
  event = ScheduledEvent.new(cron: cron, scheduled_time: scheduled_time)
  target = resolve_app
  raise 'no app registered for Cloudflare::Scheduled' if target.nil?
  target.dispatch_scheduled(event, js_env, js_ctx).__await__
end

.dispatch_js(js_event, js_env, js_ctx) ⇒ Object

Called from the JS hook. Resolves the Ruby app, builds a ScheduledEvent, runs ‘dispatch_scheduled` on the app class, and returns the result Hash for diagnostics. We `__await__` the inner dispatch so the returned object is the resolved Hash, not a Promise — the JS hook then `await`s our return promise once.



118
119
120
121
122
123
124
125
126
# File 'lib/cloudflare_workers/scheduled.rb', line 118

def self.dispatch_js(js_event, js_env, js_ctx)
  event = ScheduledEvent.from_js(js_event)
  target = resolve_app
  if target.nil?
    warn '[Cloudflare::Scheduled] no app registered; ignoring scheduled event'
    return { 'fired' => 0, 'total' => 0, 'results' => [], 'error' => 'no_app' }
  end
  target.dispatch_scheduled(event, js_env, js_ctx).__await__
end

.install_dispatcherObject

Install the JS-side dispatcher on globalThis. Idempotent — safe to call multiple times (last writer wins).

NOTE: kept as a single-line backtick x-string. Opal’s parser treats multi-line backtick strings as raw statements that don’t return a value AND it tries to lex-as-Ruby everything inside, so JS comments containing the Ruby backtick delimiter (‘…`) crash the build. Single-line form sidesteps both pitfalls.



108
109
110
111
# File 'lib/cloudflare_workers/scheduled.rb', line 108

def self.install_dispatcher
  mod = self
  `globalThis.__HOMURA_SCHEDULED_DISPATCH__ = async function(js_event, js_env, js_ctx) { try { return await #{mod}.$dispatch_js(js_event, js_env, js_ctx); } catch (err) { try { globalThis.console.error('[Cloudflare::Scheduled] dispatch failed:', err && err.stack || err); } catch (e) {} return { error: String(err && err.message || err) }; } };(function(){var g=globalThis;g.__OPAL_WORKERS__=g.__OPAL_WORKERS__||{};g.__OPAL_WORKERS__.scheduled=g.__HOMURA_SCHEDULED_DISPATCH__;})();`
end

.resolve_appObject

Resolve which app class should receive the dispatch. Priority:

1. `Cloudflare::Scheduled.app = SomeApp`     (explicit override)
2. `Rack::Handler::CloudflareWorkers.app`    (set by `run app`)

Returns the class itself (not an instance), because ‘dispatch_scheduled` is a class method on Sinatra apps.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/cloudflare_workers/scheduled.rb', line 160

def self.resolve_app
  candidate = @app
  if candidate.nil? && defined?(::Rack::Handler::CloudflareWorkers)
    candidate = ::Rack::Handler::CloudflareWorkers.app
  end
  return nil if candidate.nil?
  # Sinatra app classes respond to `dispatch_scheduled` (added by
  # Sinatra::Scheduled). Plain Rack apps would be instances and
  # lack the class method — we return them as-is and let the
  # caller's `dispatch_scheduled` raise NoMethodError so the
  # mistake is visible.
  if candidate.respond_to?(:dispatch_scheduled)
    candidate
  elsif candidate.respond_to?(:class) && candidate.class.respond_to?(:dispatch_scheduled)
    candidate.class
  else
    candidate
  end
end