homura-runtime 0.3.7 → 0.3.9
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 +17 -0
- data/exe/auto-await +23 -23
- data/exe/compile-assets +68 -61
- data/exe/compile-erb +263 -255
- data/exe/homura-build +74 -21
- data/lib/homura/runtime/ai.rb +104 -85
- data/lib/homura/runtime/async_registry.rb +124 -109
- data/lib/homura/runtime/auto_await/analyzer.rb +28 -15
- data/lib/homura/runtime/auto_await/transformer.rb +1 -0
- data/lib/homura/runtime/build_support.rb +90 -11
- data/lib/homura/runtime/cache.rb +21 -18
- data/lib/homura/runtime/durable_object.rb +27 -17
- data/lib/homura/runtime/email.rb +14 -14
- data/lib/homura/runtime/http.rb +4 -3
- data/lib/homura/runtime/multipart.rb +11 -4
- data/lib/homura/runtime/queue.rb +53 -23
- data/lib/homura/runtime/scheduled.rb +12 -14
- data/lib/homura/runtime/stream.rb +18 -14
- data/lib/homura/runtime/version.rb +1 -1
- data/lib/homura/runtime.rb +148 -91
- data/lib/homura_vendor_tempfile.rb +4 -2
- data/lib/homura_vendor_tilt.rb +5 -3
- data/lib/homura_vendor_zlib.rb +3 -0
- data/lib/opal_patches.rb +83 -66
- metadata +1 -1
data/lib/homura/runtime/cache.rb
CHANGED
|
@@ -74,8 +74,7 @@ module Cloudflare
|
|
|
74
74
|
# don't re-open the handle.
|
|
75
75
|
def self.open(name)
|
|
76
76
|
name_str = name.to_s
|
|
77
|
-
js_promise =
|
|
78
|
-
`(typeof caches !== 'undefined' && caches && caches.open ? caches.open(#{name_str}) : Promise.resolve(null))`
|
|
77
|
+
js_promise = `(typeof caches !== 'undefined' && caches && caches.open ? caches.open(#{name_str}) : Promise.resolve(null))`
|
|
79
78
|
js_cache = js_promise.__await__
|
|
80
79
|
Cache.new(js_cache, name_str)
|
|
81
80
|
end
|
|
@@ -113,22 +112,20 @@ module Cloudflare
|
|
|
113
112
|
# JS Request. Derive the URL from the same shapes supported by
|
|
114
113
|
# `request_to_js` so the `url` field on the returned
|
|
115
114
|
# HTTPResponse is always a real URL.
|
|
116
|
-
url_str =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
end
|
|
115
|
+
url_str = if request_or_url.is_a?(String)
|
|
116
|
+
request_or_url
|
|
117
|
+
elsif defined?(Cloudflare::HTTPResponse) &&
|
|
118
|
+
request_or_url.is_a?(Cloudflare::HTTPResponse)
|
|
119
|
+
request_or_url.url.to_s
|
|
120
|
+
elsif `(#{request_or_url} != null && typeof #{request_or_url} === 'object' && typeof #{request_or_url}.url === 'string')`
|
|
121
|
+
`String(#{request_or_url}.url)`
|
|
122
|
+
else
|
|
123
|
+
request_or_url.to_s
|
|
124
|
+
end
|
|
127
125
|
|
|
128
126
|
# Single-line backtick IIFE — see `put` for the Opal multi-line
|
|
129
127
|
# x-string quirk that silently drops the returned Promise.
|
|
130
|
-
js_promise =
|
|
131
|
-
`(async function(js, req, Kernel, err_klass) { if (js == null || js === Opal.nil) return null; var cached; try { cached = await js.match(req); } catch (e) { Kernel.$raise(err_klass.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'match' }))); } if (cached == null) return null; var text = ''; try { text = await cached.text(); } catch (_) { text = ''; } var hk = []; var hv = []; if (cached.headers && typeof cached.headers.forEach === 'function') { cached.headers.forEach(function(v, k) { hk.push(String(k).toLowerCase()); hv.push(String(v)); }); } return { status: cached.status|0, text: text, hkeys: hk, hvals: hv }; })(#{js}, #{req}, #{Kernel}, #{err_klass})`
|
|
128
|
+
js_promise = `(async function(js, req, Kernel, err_klass) { if (js == null || js === Opal.nil) return null; var cached; try { cached = await js.match(req); } catch (e) { Kernel.$raise(err_klass.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'match' }))); } if (cached == null) return null; var text = ''; try { text = await cached.text(); } catch (_) { text = ''; } var hk = []; var hv = []; if (cached.headers && typeof cached.headers.forEach === 'function') { cached.headers.forEach(function(v, k) { hk.push(String(k).toLowerCase()); hv.push(String(v)); }); } return { status: cached.status|0, text: text, hkeys: hk, hvals: hv }; })(#{js}, #{req}, #{Kernel}, #{err_klass})`
|
|
132
129
|
js_result = js_promise.__await__
|
|
133
130
|
return nil if `#{js_result} == null`
|
|
134
131
|
|
|
@@ -141,6 +138,7 @@ module Cloudflare
|
|
|
141
138
|
h[`#{hkeys}[#{i}]`] = `#{hvals}[#{i}]`
|
|
142
139
|
i += 1
|
|
143
140
|
end
|
|
141
|
+
|
|
144
142
|
response_klass.new(
|
|
145
143
|
status: `#{js_result}.status`,
|
|
146
144
|
headers: h,
|
|
@@ -212,15 +210,19 @@ module Cloudflare
|
|
|
212
210
|
def request_to_js(request_or_url)
|
|
213
211
|
return `new Request(#{request_or_url})` if request_or_url.is_a?(String)
|
|
214
212
|
if defined?(Cloudflare::HTTPResponse) &&
|
|
215
|
-
|
|
213
|
+
request_or_url.is_a?(Cloudflare::HTTPResponse)
|
|
216
214
|
url_str = request_or_url.url.to_s
|
|
217
215
|
return `new Request(#{url_str})`
|
|
218
216
|
end
|
|
217
|
+
|
|
219
218
|
if `(#{request_or_url} != null && typeof #{request_or_url} === 'object' && typeof #{request_or_url}.url === 'string')`
|
|
220
219
|
return request_or_url
|
|
221
220
|
end
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
|
|
222
|
+
raise(
|
|
223
|
+
ArgumentError,
|
|
224
|
+
"Cloudflare::Cache request must be a String URL, Cloudflare::HTTPResponse, or JS Request (got #{request_or_url.class})"
|
|
225
|
+
)
|
|
224
226
|
end
|
|
225
227
|
|
|
226
228
|
def ruby_headers_to_js(hash)
|
|
@@ -230,6 +232,7 @@ module Cloudflare
|
|
|
230
232
|
vs = v.to_s
|
|
231
233
|
`#{js_obj}[#{ks}] = #{vs}`
|
|
232
234
|
end
|
|
235
|
+
|
|
233
236
|
js_obj
|
|
234
237
|
end
|
|
235
238
|
end
|
|
@@ -167,8 +167,7 @@ module Cloudflare
|
|
|
167
167
|
|
|
168
168
|
# Single-line IIFE — see `lib/homura/runtime/cache.rb#put`
|
|
169
169
|
# for why Opal can silently drop a multi-line x-string Promise.
|
|
170
|
-
js_promise =
|
|
171
|
-
`(async function(stub, url_str, method_str, js_headers, js_body, Kernel, err_klass, do_class_label) { var init = { method: method_str, headers: js_headers }; if (js_body !== null && js_body !== undefined && js_body !== Opal.nil) { init.body = js_body; } var resp; try { resp = await stub.fetch(url_str, init); } catch (e) { Kernel.$raise(err_klass.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'stub.fetch', do_class: do_class_label }))); } var text = ''; try { text = await resp.text(); } catch (_) { text = ''; } var hk = []; var hv = []; if (resp.headers && typeof resp.headers.forEach === 'function') { resp.headers.forEach(function(v, k) { hk.push(String(k).toLowerCase()); hv.push(String(v)); }); } return { status: resp.status|0, text: text, hkeys: hk, hvals: hv }; })(#{js_stub}, #{url_str}, #{method_str}, #{js_headers}, #{js_body}, #{Kernel}, #{err_klass}, #{do_class_label})`
|
|
170
|
+
js_promise = `(async function(stub, url_str, method_str, js_headers, js_body, Kernel, err_klass, do_class_label) { var init = { method: method_str, headers: js_headers }; if (js_body !== null && js_body !== undefined && js_body !== Opal.nil) { init.body = js_body; } var resp; try { resp = await stub.fetch(url_str, init); } catch (e) { Kernel.$raise(err_klass.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'stub.fetch', do_class: do_class_label }))); } var text = ''; try { text = await resp.text(); } catch (_) { text = ''; } var hk = []; var hv = []; if (resp.headers && typeof resp.headers.forEach === 'function') { resp.headers.forEach(function(v, k) { hk.push(String(k).toLowerCase()); hv.push(String(v)); }); } return { status: resp.status|0, text: text, hkeys: hk, hvals: hv }; })(#{js_stub}, #{url_str}, #{method_str}, #{js_headers}, #{js_body}, #{Kernel}, #{err_klass}, #{do_class_label})`
|
|
172
171
|
|
|
173
172
|
js_result = js_promise.__await__
|
|
174
173
|
hkeys = `#{js_result}.hkeys`
|
|
@@ -198,6 +197,7 @@ module Cloudflare
|
|
|
198
197
|
hdrs["content-type"] = "application/json"
|
|
199
198
|
end
|
|
200
199
|
end
|
|
200
|
+
|
|
201
201
|
fetch(path, method: method, headers: hdrs, body: request_body)
|
|
202
202
|
end
|
|
203
203
|
|
|
@@ -258,11 +258,11 @@ module Cloudflare
|
|
|
258
258
|
unless class_name.is_a?(String)
|
|
259
259
|
raise ArgumentError, "class_name must be a String"
|
|
260
260
|
end
|
|
261
|
+
|
|
261
262
|
@handlers ||= {}
|
|
262
263
|
# Wrap via define_method so Opal's `# await: true` picks it up as
|
|
263
264
|
# async (same trick Sinatra::Scheduled uses for its jobs).
|
|
264
|
-
method_name =
|
|
265
|
-
"__do_handler_#{class_name.gsub(/[^A-Za-z0-9_]/, "_")}".to_sym
|
|
265
|
+
method_name = "__do_handler_#{class_name.gsub(/[^A-Za-z0-9_]/, "_")}".to_sym
|
|
266
266
|
DurableObjectRequestContext.send(:define_method, method_name, &block)
|
|
267
267
|
unbound = DurableObjectRequestContext.instance_method(method_name)
|
|
268
268
|
DurableObjectRequestContext.send(:remove_method, method_name)
|
|
@@ -317,9 +317,7 @@ module Cloudflare
|
|
|
317
317
|
body = {
|
|
318
318
|
"error" => "no Ruby handler for DurableObject class #{class_name}"
|
|
319
319
|
}.to_json
|
|
320
|
-
return(
|
|
321
|
-
build_js_response(500, { "content-type" => "application/json" }, body)
|
|
322
|
-
)
|
|
320
|
+
return (build_js_response(500, {"content-type" => "application/json"}, body))
|
|
323
321
|
end
|
|
324
322
|
|
|
325
323
|
state = DurableObjectState.new(js_state)
|
|
@@ -354,7 +352,7 @@ module Cloudflare
|
|
|
354
352
|
(result["body"] || result[:body] || "").to_s
|
|
355
353
|
]
|
|
356
354
|
else
|
|
357
|
-
[200, {
|
|
355
|
+
[200, {"content-type" => "text/plain; charset=utf-8"}, result.to_s]
|
|
358
356
|
end
|
|
359
357
|
end
|
|
360
358
|
|
|
@@ -365,6 +363,7 @@ module Cloudflare
|
|
|
365
363
|
vs = v.to_s
|
|
366
364
|
`#{js_headers}[#{ks}] = #{vs}`
|
|
367
365
|
end
|
|
366
|
+
|
|
368
367
|
status_int = status.to_i
|
|
369
368
|
body_str = body.to_s
|
|
370
369
|
`new Response(#{body_str}, { status: #{status_int}, headers: #{js_headers} })`
|
|
@@ -475,10 +474,12 @@ module Cloudflare
|
|
|
475
474
|
ts = t.to_s
|
|
476
475
|
`#{js_tags}.push(#{ts})`
|
|
477
476
|
end
|
|
477
|
+
|
|
478
478
|
`#{js_state}.acceptWebSocket(#{js_ws}, #{js_tags})`
|
|
479
479
|
else
|
|
480
480
|
`#{js_state}.acceptWebSocket(#{js_ws})`
|
|
481
481
|
end
|
|
482
|
+
|
|
482
483
|
nil
|
|
483
484
|
end
|
|
484
485
|
|
|
@@ -487,13 +488,13 @@ module Cloudflare
|
|
|
487
488
|
# `getWebSockets(tag)`.
|
|
488
489
|
def web_sockets(tag: nil)
|
|
489
490
|
js_state = @js_state
|
|
490
|
-
js_arr =
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
491
|
+
js_arr = if tag
|
|
492
|
+
ts = tag.to_s
|
|
493
|
+
`(#{js_state}.getWebSockets ? #{js_state}.getWebSockets(#{ts}) : [])`
|
|
494
|
+
else
|
|
495
|
+
`(#{js_state}.getWebSockets ? #{js_state}.getWebSockets() : [])`
|
|
496
|
+
end
|
|
497
|
+
|
|
497
498
|
out = []
|
|
498
499
|
len = `#{js_arr}.length`
|
|
499
500
|
i = 0
|
|
@@ -501,6 +502,7 @@ module Cloudflare
|
|
|
501
502
|
out << `#{js_arr}[#{i}]`
|
|
502
503
|
i += 1
|
|
503
504
|
end
|
|
505
|
+
|
|
504
506
|
out
|
|
505
507
|
end
|
|
506
508
|
end
|
|
@@ -567,8 +569,7 @@ module Cloudflare
|
|
|
567
569
|
`#{js_opts}.reverse = #{!!reverse}` unless reverse.nil?
|
|
568
570
|
`#{js_opts}.start = #{start.to_s}` unless start.nil?
|
|
569
571
|
`#{js_opts}.end = #{end_key.to_s}` unless end_key.nil?
|
|
570
|
-
js_promise =
|
|
571
|
-
`#{js}.list(#{js_opts}).catch(function(e) { #{Kernel}.$raise(#{err_klass}.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'storage.list' }))); })`
|
|
572
|
+
js_promise = `#{js}.list(#{js_opts}).catch(function(e) { #{Kernel}.$raise(#{err_klass}.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'storage.list' }))); })`
|
|
572
573
|
js_result = js_promise.__await__
|
|
573
574
|
out = {}
|
|
574
575
|
return out if `#{js_result} == null`
|
|
@@ -646,9 +647,11 @@ module Cloudflare
|
|
|
646
647
|
def storage
|
|
647
648
|
@state.storage
|
|
648
649
|
end
|
|
650
|
+
|
|
649
651
|
def cf_env
|
|
650
652
|
env["cloudflare.env"]
|
|
651
653
|
end
|
|
654
|
+
|
|
652
655
|
def cf_ctx
|
|
653
656
|
env["cloudflare.ctx"]
|
|
654
657
|
end
|
|
@@ -656,24 +659,31 @@ module Cloudflare
|
|
|
656
659
|
def d1
|
|
657
660
|
env["cloudflare.DB"]
|
|
658
661
|
end
|
|
662
|
+
|
|
659
663
|
def db
|
|
660
664
|
d1
|
|
661
665
|
end
|
|
666
|
+
|
|
662
667
|
def kv
|
|
663
668
|
env["cloudflare.KV"]
|
|
664
669
|
end
|
|
670
|
+
|
|
665
671
|
def bucket
|
|
666
672
|
env["cloudflare.BUCKET"]
|
|
667
673
|
end
|
|
674
|
+
|
|
668
675
|
def ai
|
|
669
676
|
Cloudflare::Bindings.ai(env)
|
|
670
677
|
end
|
|
678
|
+
|
|
671
679
|
def send_email
|
|
672
680
|
env["cloudflare.SEND_EMAIL"]
|
|
673
681
|
end
|
|
682
|
+
|
|
674
683
|
def jobs_queue
|
|
675
684
|
env["cloudflare.QUEUE_JOBS"]
|
|
676
685
|
end
|
|
686
|
+
|
|
677
687
|
def durable_object(name, id_or_name = nil)
|
|
678
688
|
Cloudflare::Bindings.durable_object(env, name, id_or_name)
|
|
679
689
|
end
|
data/lib/homura/runtime/email.rb
CHANGED
|
@@ -52,20 +52,18 @@ module Cloudflare
|
|
|
52
52
|
has_html = !(html.nil? || html.to_s.empty?)
|
|
53
53
|
raise Error, "text or html is required" unless has_text || has_html
|
|
54
54
|
|
|
55
|
-
payload =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
55
|
+
payload = build_send_payload(
|
|
56
|
+
to: to,
|
|
57
|
+
from: from,
|
|
58
|
+
subject: subject.to_s,
|
|
59
|
+
text: text,
|
|
60
|
+
html: html,
|
|
61
|
+
reply_to: reply_to
|
|
62
|
+
)
|
|
64
63
|
|
|
65
64
|
cf = Cloudflare
|
|
66
65
|
# 多行 x-string をメソッド末尾に置くと Opal が Promise を返さない出力になることがあるため return を明示する。
|
|
67
|
-
return(
|
|
68
|
-
`(async function(binding, payload, Kernel, Err, cf) {
|
|
66
|
+
return (`(async function(binding, payload, Kernel, Err, cf) {
|
|
69
67
|
try {
|
|
70
68
|
var r = await binding.send(payload);
|
|
71
69
|
if (r == null || r === undefined) {
|
|
@@ -83,8 +81,7 @@ module Cloudflare
|
|
|
83
81
|
var msg = (e && e.message) ? String(e.message) : String(e);
|
|
84
82
|
Kernel.$raise(Err.$new(msg, Opal.hash({ code: code })));
|
|
85
83
|
}
|
|
86
|
-
})(#{js}, #{payload}, #{Kernel}, #{err_klass}, #{cf})`
|
|
87
|
-
)
|
|
84
|
+
})(#{js}, #{payload}, #{Kernel}, #{err_klass}, #{cf})`)
|
|
88
85
|
end
|
|
89
86
|
|
|
90
87
|
private
|
|
@@ -128,9 +125,11 @@ module Cloudflare
|
|
|
128
125
|
if e[:name] && !e[:name].to_s.strip.empty?
|
|
129
126
|
`#{js}.name = #{e[:name].to_s}`
|
|
130
127
|
end
|
|
128
|
+
|
|
131
129
|
`#{arr}.push(#{js})`
|
|
132
130
|
end
|
|
133
131
|
end
|
|
132
|
+
|
|
134
133
|
arr
|
|
135
134
|
end
|
|
136
135
|
|
|
@@ -149,8 +148,9 @@ module Cloudflare
|
|
|
149
148
|
if nm.nil? || nm.to_s.strip.empty?
|
|
150
149
|
[em.to_s.strip]
|
|
151
150
|
else
|
|
152
|
-
[{
|
|
151
|
+
[{email: em.to_s.strip, name: nm.to_s}]
|
|
153
152
|
end
|
|
153
|
+
|
|
154
154
|
when Array
|
|
155
155
|
raw.flat_map { |x| flatten_recipients(x) }
|
|
156
156
|
else
|
data/lib/homura/runtime/http.rb
CHANGED
|
@@ -76,7 +76,8 @@ module Cloudflare
|
|
|
76
76
|
response_klass = Cloudflare::HTTPResponse
|
|
77
77
|
err_klass = Cloudflare::HTTPError
|
|
78
78
|
headers_to_hash = method(:js_headers_to_hash)
|
|
79
|
-
|
|
79
|
+
# silence opal lint
|
|
80
|
+
_ = headers_to_hash
|
|
80
81
|
|
|
81
82
|
# NOTE: the multi-line backtick below returns a Promise BECAUSE it
|
|
82
83
|
# is assigned to `js_promise` (and `js_promise.__await__` awaits
|
|
@@ -87,8 +88,7 @@ module Cloudflare
|
|
|
87
88
|
# expression. See the single-line IIFE pattern used in
|
|
88
89
|
# lib/homura/runtime/{cache,queue,durable_object}.rb#put for
|
|
89
90
|
# the alternative that survives either position. (Phase 11B audit.)
|
|
90
|
-
js_promise =
|
|
91
|
-
`
|
|
91
|
+
js_promise = `
|
|
92
92
|
(async function() {
|
|
93
93
|
var init = { method: #{method_str}, headers: #{js_headers}, redirect: 'follow' };
|
|
94
94
|
if (#{js_body} !== nil && #{js_body} != null) { init.body = #{js_body}; }
|
|
@@ -144,6 +144,7 @@ module Cloudflare
|
|
|
144
144
|
vs = v.to_s
|
|
145
145
|
`#{js_obj}[#{ks}] = #{vs}`
|
|
146
146
|
end
|
|
147
|
+
|
|
147
148
|
js_obj
|
|
148
149
|
end
|
|
149
150
|
|
|
@@ -56,6 +56,7 @@ module Cloudflare
|
|
|
56
56
|
def size
|
|
57
57
|
@bytes_binstr.length
|
|
58
58
|
end
|
|
59
|
+
|
|
59
60
|
alias_method :bytesize, :size
|
|
60
61
|
|
|
61
62
|
# Convenience accessor matching the CRuby Rack shape.
|
|
@@ -106,6 +107,7 @@ module Cloudflare
|
|
|
106
107
|
tempfile: self
|
|
107
108
|
}
|
|
108
109
|
end
|
|
110
|
+
|
|
109
111
|
alias_method :to_hash, :to_h
|
|
110
112
|
|
|
111
113
|
# `#[]` so `file[:filename]` works on the UploadedFile itself
|
|
@@ -133,9 +135,11 @@ module Cloudflare
|
|
|
133
135
|
if (m = ct.match(/boundary="([^"]+)"/i))
|
|
134
136
|
return m[1]
|
|
135
137
|
end
|
|
138
|
+
|
|
136
139
|
if (m = ct.match(/boundary=([^;,\s]+)/i))
|
|
137
140
|
return m[1]
|
|
138
141
|
end
|
|
142
|
+
|
|
139
143
|
nil
|
|
140
144
|
end
|
|
141
145
|
|
|
@@ -154,7 +158,8 @@ module Cloudflare
|
|
|
154
158
|
sep = "--" + boundary
|
|
155
159
|
term = "--" + boundary + "--"
|
|
156
160
|
sep_line = sep + CRLF
|
|
157
|
-
|
|
161
|
+
# the very first boundary may skip the leading CRLF
|
|
162
|
+
sep_last = sep + CRLF
|
|
158
163
|
body = body_binstr.to_s
|
|
159
164
|
|
|
160
165
|
# Skip any preamble before the first boundary.
|
|
@@ -251,13 +256,14 @@ module Cloudflare
|
|
|
251
256
|
# Quoted `key="value"`
|
|
252
257
|
q_re = /(?:^|[;\s])#{k}\s*=\s*"((?:\\"|[^"])*)"/i
|
|
253
258
|
if (m = disposition.match(q_re))
|
|
254
|
-
return m[1].gsub(
|
|
259
|
+
return m[1].gsub("\\\"", "\"")
|
|
255
260
|
end
|
|
256
261
|
# Bare `key=value`
|
|
257
262
|
b_re = /(?:^|[;\s])#{k}\s*=\s*([^;]+)/i
|
|
258
263
|
if (m = disposition.match(b_re))
|
|
259
264
|
return m[1].strip
|
|
260
265
|
end
|
|
266
|
+
|
|
261
267
|
nil
|
|
262
268
|
end
|
|
263
269
|
|
|
@@ -276,10 +282,10 @@ module Cloudflare
|
|
|
276
282
|
return cached if cached
|
|
277
283
|
|
|
278
284
|
ct = env["CONTENT_TYPE"]
|
|
279
|
-
return({}) unless ct && ct.to_s.downcase.include?("multipart/")
|
|
285
|
+
return ({}) unless ct && ct.to_s.downcase.include?("multipart/")
|
|
280
286
|
|
|
281
287
|
io = env["rack.input"]
|
|
282
|
-
return({}) if io.nil?
|
|
288
|
+
return ({}) if io.nil?
|
|
283
289
|
|
|
284
290
|
# `rack.input` is normally a StringIO wrapping the body_binstr
|
|
285
291
|
# we staged in src/worker.mjs. Read the full body; it's already
|
|
@@ -292,6 +298,7 @@ module Cloudflare
|
|
|
292
298
|
# some stubs don't support rewind — ignore
|
|
293
299
|
end
|
|
294
300
|
end
|
|
301
|
+
|
|
295
302
|
body = io.respond_to?(:read) ? io.read.to_s : ""
|
|
296
303
|
|
|
297
304
|
parsed = parse(body, ct)
|
data/lib/homura/runtime/queue.rb
CHANGED
|
@@ -82,11 +82,13 @@ module Cloudflare
|
|
|
82
82
|
qname = @name
|
|
83
83
|
err_klass = Cloudflare::QueueError
|
|
84
84
|
unless available?
|
|
85
|
-
raise
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
raise(
|
|
86
|
+
QueueError.new(
|
|
87
|
+
"queue binding not bound",
|
|
88
|
+
operation: "send",
|
|
89
|
+
queue: qname
|
|
90
|
+
)
|
|
91
|
+
)
|
|
90
92
|
end
|
|
91
93
|
|
|
92
94
|
js_body = ruby_to_js(body)
|
|
@@ -108,11 +110,13 @@ module Cloudflare
|
|
|
108
110
|
qname = @name
|
|
109
111
|
err_klass = Cloudflare::QueueError
|
|
110
112
|
unless available?
|
|
111
|
-
raise
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
raise(
|
|
114
|
+
QueueError.new(
|
|
115
|
+
"queue binding not bound",
|
|
116
|
+
operation: "send_batch",
|
|
117
|
+
queue: qname
|
|
118
|
+
)
|
|
119
|
+
)
|
|
116
120
|
end
|
|
117
121
|
|
|
118
122
|
js_msgs = `([])`
|
|
@@ -131,6 +135,7 @@ module Cloudflare
|
|
|
131
135
|
`#{js_msgs}.push({ body: #{js_body} })`
|
|
132
136
|
end
|
|
133
137
|
end
|
|
138
|
+
|
|
134
139
|
js_opts = `({})`
|
|
135
140
|
`#{js_opts}.delaySeconds = #{delay_seconds.to_i}` if delay_seconds
|
|
136
141
|
|
|
@@ -143,10 +148,14 @@ module Cloudflare
|
|
|
143
148
|
# Same Ruby→JS conversion that Cloudflare::AI.ruby_to_js uses, but
|
|
144
149
|
# local to avoid cross-wrapper coupling.
|
|
145
150
|
def ruby_to_js(val)
|
|
146
|
-
if val.is_a?(String) ||
|
|
147
|
-
|
|
151
|
+
if val.is_a?(String) ||
|
|
152
|
+
val.is_a?(Numeric) ||
|
|
153
|
+
val == true ||
|
|
154
|
+
val == false ||
|
|
155
|
+
val.nil?
|
|
148
156
|
return val
|
|
149
157
|
end
|
|
158
|
+
|
|
150
159
|
return val.to_s if val.is_a?(Symbol)
|
|
151
160
|
if val.is_a?(Hash)
|
|
152
161
|
obj = `({})`
|
|
@@ -155,16 +164,20 @@ module Cloudflare
|
|
|
155
164
|
jv = ruby_to_js(v)
|
|
156
165
|
`#{obj}[#{ks}] = #{jv}`
|
|
157
166
|
end
|
|
167
|
+
|
|
158
168
|
return obj
|
|
159
169
|
end
|
|
170
|
+
|
|
160
171
|
if val.is_a?(Array)
|
|
161
172
|
arr = `([])`
|
|
162
173
|
val.each do |v|
|
|
163
174
|
jv = ruby_to_js(v)
|
|
164
175
|
`#{arr}.push(#{jv})`
|
|
165
176
|
end
|
|
177
|
+
|
|
166
178
|
return arr
|
|
167
179
|
end
|
|
180
|
+
|
|
168
181
|
val
|
|
169
182
|
end
|
|
170
183
|
end
|
|
@@ -185,8 +198,7 @@ module Cloudflare
|
|
|
185
198
|
|
|
186
199
|
def timestamp
|
|
187
200
|
js = @js
|
|
188
|
-
ms =
|
|
189
|
-
`(#{js} && #{js}.timestamp && typeof #{js}.timestamp.getTime === 'function' ? #{js}.timestamp.getTime() : null)`
|
|
201
|
+
ms = `(#{js} && #{js}.timestamp && typeof #{js}.timestamp.getTime === 'function' ? #{js}.timestamp.getTime() : null)`
|
|
190
202
|
return nil if `#{ms} == null`
|
|
191
203
|
Time.at(ms.to_f / 1000.0)
|
|
192
204
|
end
|
|
@@ -234,6 +246,7 @@ module Cloudflare
|
|
|
234
246
|
else
|
|
235
247
|
`(#{js} && typeof #{js}.retry === 'function' ? #{js}.retry() : null)`
|
|
236
248
|
end
|
|
249
|
+
|
|
237
250
|
nil
|
|
238
251
|
end
|
|
239
252
|
|
|
@@ -244,6 +257,7 @@ module Cloudflare
|
|
|
244
257
|
if `typeof #{v} === 'string' || typeof #{v} === 'number' || typeof #{v} === 'boolean'`
|
|
245
258
|
return v
|
|
246
259
|
end
|
|
260
|
+
|
|
247
261
|
if `Array.isArray(#{v})`
|
|
248
262
|
out = []
|
|
249
263
|
len = `#{v}.length`
|
|
@@ -252,8 +266,10 @@ module Cloudflare
|
|
|
252
266
|
out << js_to_ruby(`#{v}[#{i}]`)
|
|
253
267
|
i += 1
|
|
254
268
|
end
|
|
269
|
+
|
|
255
270
|
return out
|
|
256
271
|
end
|
|
272
|
+
|
|
257
273
|
if `typeof #{v} === 'object'`
|
|
258
274
|
h = {}
|
|
259
275
|
keys = `Object.keys(#{v})`
|
|
@@ -264,8 +280,10 @@ module Cloudflare
|
|
|
264
280
|
h[k] = js_to_ruby(`#{v}[#{k}]`)
|
|
265
281
|
i += 1
|
|
266
282
|
end
|
|
283
|
+
|
|
267
284
|
return h
|
|
268
285
|
end
|
|
286
|
+
|
|
269
287
|
v
|
|
270
288
|
end
|
|
271
289
|
end
|
|
@@ -296,6 +314,7 @@ module Cloudflare
|
|
|
296
314
|
out << QueueMessage.new(`#{arr}[#{i}]`)
|
|
297
315
|
i += 1
|
|
298
316
|
end
|
|
317
|
+
|
|
299
318
|
@messages = out
|
|
300
319
|
end
|
|
301
320
|
|
|
@@ -317,6 +336,7 @@ module Cloudflare
|
|
|
317
336
|
else
|
|
318
337
|
`(#{js} && typeof #{js}.retryAll === 'function' ? #{js}.retryAll() : null)`
|
|
319
338
|
end
|
|
339
|
+
|
|
320
340
|
nil
|
|
321
341
|
end
|
|
322
342
|
end
|
|
@@ -353,15 +373,15 @@ module Cloudflare
|
|
|
353
373
|
queue_name = batch.queue
|
|
354
374
|
handler = handler_for(queue_name)
|
|
355
375
|
if handler.nil?
|
|
356
|
-
warn
|
|
357
|
-
|
|
358
|
-
{
|
|
359
|
-
"queue" => queue_name,
|
|
360
|
-
"handled" => false,
|
|
361
|
-
"size" => batch.size,
|
|
362
|
-
"reason" => "no_handler"
|
|
363
|
-
}
|
|
376
|
+
warn(
|
|
377
|
+
"[Cloudflare::QueueConsumer] no handler registered for queue #{queue_name.inspect}; messages will time out and retry"
|
|
364
378
|
)
|
|
379
|
+
return ({
|
|
380
|
+
"queue" => queue_name,
|
|
381
|
+
"handled" => false,
|
|
382
|
+
"size" => batch.size,
|
|
383
|
+
"reason" => "no_handler"
|
|
384
|
+
})
|
|
365
385
|
end
|
|
366
386
|
|
|
367
387
|
ctx = QueueContext.new(batch, js_env, js_ctx)
|
|
@@ -369,6 +389,7 @@ module Cloudflare
|
|
|
369
389
|
if `(#{result} != null && typeof #{result}.then === 'function')`
|
|
370
390
|
result = result.__await__
|
|
371
391
|
end
|
|
392
|
+
|
|
372
393
|
{
|
|
373
394
|
"queue" => queue_name,
|
|
374
395
|
"handled" => true,
|
|
@@ -403,30 +424,39 @@ module Cloudflare
|
|
|
403
424
|
def d1
|
|
404
425
|
env["cloudflare.DB"]
|
|
405
426
|
end
|
|
427
|
+
|
|
406
428
|
def db
|
|
407
429
|
d1
|
|
408
430
|
end
|
|
431
|
+
|
|
409
432
|
def cf_env
|
|
410
433
|
env["cloudflare.env"]
|
|
411
434
|
end
|
|
435
|
+
|
|
412
436
|
def cf_ctx
|
|
413
437
|
env["cloudflare.ctx"]
|
|
414
438
|
end
|
|
439
|
+
|
|
415
440
|
def kv
|
|
416
441
|
env["cloudflare.KV"]
|
|
417
442
|
end
|
|
443
|
+
|
|
418
444
|
def bucket
|
|
419
445
|
env["cloudflare.BUCKET"]
|
|
420
446
|
end
|
|
447
|
+
|
|
421
448
|
def ai
|
|
422
449
|
Cloudflare::Bindings.ai(env)
|
|
423
450
|
end
|
|
451
|
+
|
|
424
452
|
def send_email
|
|
425
453
|
env["cloudflare.SEND_EMAIL"]
|
|
426
454
|
end
|
|
455
|
+
|
|
427
456
|
def jobs_queue
|
|
428
457
|
env["cloudflare.QUEUE_JOBS"]
|
|
429
458
|
end
|
|
459
|
+
|
|
430
460
|
def durable_object(name, id_or_name = nil)
|
|
431
461
|
Cloudflare::Bindings.durable_object(env, name, id_or_name)
|
|
432
462
|
end
|
|
@@ -446,7 +476,7 @@ module Cloudflare
|
|
|
446
476
|
Cloudflare::Bindings.build_env(
|
|
447
477
|
js_env,
|
|
448
478
|
@js_ctx,
|
|
449
|
-
{
|
|
479
|
+
{"cloudflare.queue" => true}
|
|
450
480
|
)
|
|
451
481
|
end
|
|
452
482
|
end
|
|
@@ -63,16 +63,14 @@ module Cloudflare
|
|
|
63
63
|
return new(cron: "", scheduled_time: Time.now) if `#{js_event} == null`
|
|
64
64
|
|
|
65
65
|
cron = `(#{js_event}.cron == null ? '' : String(#{js_event}.cron))`
|
|
66
|
-
type =
|
|
67
|
-
`(#{js_event}.type == null ? 'scheduled' : String(#{js_event}.type))`
|
|
66
|
+
type = `(#{js_event}.type == null ? 'scheduled' : String(#{js_event}.type))`
|
|
68
67
|
has_sched = `(#{js_event}.scheduledTime != null)`
|
|
69
|
-
sched_t =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
end
|
|
68
|
+
sched_t = if has_sched
|
|
69
|
+
sched_ms = `Number(#{js_event}.scheduledTime)`
|
|
70
|
+
Time.at(sched_ms.to_f / 1000.0)
|
|
71
|
+
else
|
|
72
|
+
Time.now
|
|
73
|
+
end
|
|
76
74
|
|
|
77
75
|
new(cron: cron, scheduled_time: sched_t, type: type, raw: js_event)
|
|
78
76
|
end
|
|
@@ -121,11 +119,10 @@ module Cloudflare
|
|
|
121
119
|
event = ScheduledEvent.from_js(js_event)
|
|
122
120
|
target = resolve_app
|
|
123
121
|
if target.nil?
|
|
124
|
-
warn
|
|
125
|
-
return(
|
|
126
|
-
{ "fired" => 0, "total" => 0, "results" => [], "error" => "no_app" }
|
|
127
|
-
)
|
|
122
|
+
warn("[Cloudflare::Scheduled] no app registered; ignoring scheduled event")
|
|
123
|
+
return ({"fired" => 0, "total" => 0, "results" => [], "error" => "no_app"})
|
|
128
124
|
end
|
|
125
|
+
|
|
129
126
|
target.dispatch_scheduled(event, js_env, js_ctx).__await__
|
|
130
127
|
end
|
|
131
128
|
|
|
@@ -171,6 +168,7 @@ module Cloudflare
|
|
|
171
168
|
if candidate.nil? && defined?(::Rack::Handler::Homura)
|
|
172
169
|
candidate = ::Rack::Handler::Homura.app
|
|
173
170
|
end
|
|
171
|
+
|
|
174
172
|
return nil if candidate.nil?
|
|
175
173
|
# Sinatra app classes respond to `dispatch_scheduled` (added by
|
|
176
174
|
# Sinatra::Scheduled). Plain Rack apps would be instances and
|
|
@@ -180,7 +178,7 @@ module Cloudflare
|
|
|
180
178
|
if candidate.respond_to?(:dispatch_scheduled)
|
|
181
179
|
candidate
|
|
182
180
|
elsif candidate.respond_to?(:class) &&
|
|
183
|
-
|
|
181
|
+
candidate.class.respond_to?(:dispatch_scheduled)
|
|
184
182
|
candidate.class
|
|
185
183
|
else
|
|
186
184
|
candidate
|