homura-runtime 0.3.8 → 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.
@@ -88,6 +88,7 @@ module Cloudflare
88
88
  # a chunked streaming response.
89
89
  def each
90
90
  end
91
+
91
92
  def close
92
93
  end
93
94
 
@@ -113,6 +114,7 @@ module Cloudflare
113
114
  rescue StandardError
114
115
  # best-effort; never let logging fail the cleanup branch
115
116
  end
117
+
116
118
  ensure
117
119
  # `.__await__` here too, for the same reason: the ensure clause
118
120
  # must not return until the writer is actually closed (otherwise
@@ -170,10 +172,10 @@ module Cloudflare
170
172
  # enforced but the promise graph stays flat (Copilot review #3 —
171
173
  # prior implementation pushed every write into an unbounded
172
174
  # array which would leak memory on long-running streams).
173
- @tail =
174
- `#{@tail}.then(function() { return #{w}.write(#{enc}.encode(#{s})); })`
175
+ @tail = `#{@tail}.then(function() { return #{w}.write(#{enc}.encode(#{s})); })`
175
176
  self
176
177
  end
178
+
177
179
  alias_method :<<, :write
178
180
 
179
181
  # Helper: emit a well-formed SSE event. `data` is split on LF and
@@ -217,7 +219,8 @@ module Cloudflare
217
219
  # error to the client via the HTTP layer. Single-line x-string
218
220
  # so Opal emits it as an expression (see Multipart#to_uint8_array
219
221
  # for the same gotcha).
220
- `(async function(t, wr){ try { await t; } catch(e) {} try { await wr.close(); } catch(e) {} })(#{tail}, #{w})`.__await__
222
+ `(async function(t, wr){ try { await t; } catch(e) {} try { await wr.close(); } catch(e) {} })(#{tail}, #{w})`
223
+ .__await__
221
224
  self
222
225
  end
223
226
 
@@ -273,22 +276,23 @@ module Sinatra
273
276
  def stream(keep_open: false, type: :plain, headers: nil, &block)
274
277
  ctx = env["cloudflare.ctx"]
275
278
  extra_headers = headers || {}
276
- merged =
277
- case type
278
- when :sse, :event_stream
279
- extra_headers # SSE defaults は SSEStream 側で入る
280
- else
281
- # Plain streaming — start from an empty default set so
282
- # the SSE headers don't get force-injected into e.g. a
283
- # log-tailing or chunked-JSON endpoint.
284
- { "content-type" => "text/plain; charset=utf-8" }.merge(extra_headers)
285
- end
279
+ merged = case type
280
+ when :sse, :event_stream
281
+ # SSE defaults は SSEStream 側で入る
282
+ extra_headers
283
+ else
284
+ # Plain streaming — start from an empty default set so
285
+ # the SSE headers don't get force-injected into e.g. a
286
+ # log-tailing or chunked-JSON endpoint.
287
+ {"content-type" => "text/plain; charset=utf-8"}.merge(extra_headers)
288
+ end
289
+
286
290
  ::Cloudflare::SSEStream.new(headers: merged, ctx: ctx, &block)
287
291
  end
288
292
 
289
293
  # Register the helper on a Sinatra app. Use `register Sinatra::Streaming`.
290
294
  def self.registered(app)
291
- app.helpers Streaming
295
+ app.helpers(Streaming)
292
296
  end
293
297
  end
294
298
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HomuraRuntime
4
- VERSION = "0.3.8"
4
+ VERSION = "0.3.9"
5
5
  end
@@ -37,7 +37,8 @@ require "await"
37
37
 
38
38
  class HomuraRuntimeIO
39
39
  def initialize(channel)
40
- @channel = channel # 'log' or 'error'
40
+ # 'log' or 'error'
41
+ @channel = channel
41
42
  @buffer = ""
42
43
  end
43
44
 
@@ -48,6 +49,7 @@ class HomuraRuntimeIO
48
49
  @buffer = @buffer + str
49
50
  written += str.length
50
51
  end
52
+
51
53
  flush_lines
52
54
  written
53
55
  end
@@ -57,14 +59,17 @@ class HomuraRuntimeIO
57
59
  emit("")
58
60
  return nil
59
61
  end
62
+
60
63
  args.each do |arg|
61
64
  if arg.is_a?(Array)
62
65
  puts(*arg)
63
66
  next
64
67
  end
68
+
65
69
  line = arg.to_s
66
70
  @buffer = @buffer + (line.end_with?("\n") ? line : line + "\n")
67
71
  end
72
+
68
73
  flush_lines
69
74
  nil
70
75
  end
@@ -85,14 +90,18 @@ class HomuraRuntimeIO
85
90
  def sync
86
91
  true
87
92
  end
93
+
88
94
  def sync=(_)
89
95
  end
96
+
90
97
  def tty?
91
98
  false
92
99
  end
100
+
93
101
  def isatty
94
102
  false
95
103
  end
104
+
96
105
  def closed?
97
106
  false
98
107
  end
@@ -119,6 +128,7 @@ $stderr = HomuraRuntimeIO.new("error")
119
128
  unless Object.const_defined?(:STDOUT) && STDOUT.is_a?(HomuraRuntimeIO)
120
129
  Object.const_set(:STDOUT, $stdout)
121
130
  end
131
+
122
132
  unless Object.const_defined?(:STDERR) && STDERR.is_a?(HomuraRuntimeIO)
123
133
  Object.const_set(:STDERR, $stderr)
124
134
  end
@@ -184,18 +194,23 @@ module Rack
184
194
  def self.call(js_req, js_env, js_ctx, body_text = "")
185
195
  if @app.nil?
186
196
  if defined?(::Sinatra::Homura) &&
187
- ::Sinatra::Homura.respond_to?(:ensure_rack_app!)
197
+ ::Sinatra::Homura.respond_to?(:ensure_rack_app!)
188
198
  ::Sinatra::Homura.ensure_rack_app!
189
199
  end
200
+
190
201
  if @app.nil?
191
- raise "`run app` was never called from user code, and no Sinatra app was discoverable (define `class App < Sinatra::Base` or use top-level classic Sinatra routes)"
202
+ raise(
203
+ "`run app` was never called from user code, and no Sinatra app was discoverable (define `class App < Sinatra::Base` or use top-level classic Sinatra routes)"
204
+ )
192
205
  end
193
206
  end
194
207
 
195
208
  env = build_rack_env(js_req, js_env, js_ctx, body_text)
196
209
  result = @app.call(env)
197
- result = result.__await__ if defined?(::Cloudflare) &&
198
- ::Cloudflare.js_promise?(result)
210
+ if defined?(::Cloudflare) &&
211
+ ::Cloudflare.js_promise?(result)
212
+ result = result.__await__
213
+ end
199
214
 
200
215
  status, headers, body = result
201
216
  build_js_response(status, headers, body)
@@ -223,7 +238,8 @@ module Rack
223
238
  path = `#{url_obj}.pathname`
224
239
  # Phase 16 docs: Sinatra + Opal builds responses with String#<< and raises on PATH_INFO `/docs/`.
225
240
  path = "/docs" if path == "/docs/"
226
- raw_qs = `#{url_obj}.search` # includes leading '?' or empty string
241
+ # includes leading '?' or empty string
242
+ raw_qs = `#{url_obj}.search`
227
243
  qs = raw_qs && raw_qs.length > 0 ? raw_qs[1..-1] : ""
228
244
  scheme = `#{url_obj}.protocol`.sub(/:\z/, "")
229
245
  host = `#{url_obj}.hostname`
@@ -240,14 +256,13 @@ module Rack
240
256
  "SERVER_PROTOCOL" => "HTTP/1.1",
241
257
  "HTTPS" => scheme == "https" ? "on" : "off",
242
258
  "rack.url_scheme" => scheme,
243
- "rack.input" =>
244
- (
245
- if body_text.nil? || body_text.empty?
246
- EMPTY_STRING_IO
247
- else
248
- StringIO.new(body_text)
249
- end
250
- ),
259
+ "rack.input" => (
260
+ if body_text.nil? || body_text.empty?
261
+ EMPTY_STRING_IO
262
+ else
263
+ StringIO.new(body_text)
264
+ end
265
+ ),
251
266
  "rack.errors" => $stderr,
252
267
  "rack.multithread" => false,
253
268
  "rack.multiprocess" => false,
@@ -304,14 +319,14 @@ module Rack
304
319
  # the JS object through unchanged — any reconstruction
305
320
  # would strip runtime-only properties the client depends on.
306
321
  first_body = (body.first if body.respond_to?(:first))
307
- first_body_ruby =
308
- !`(#{first_body} == null || #{first_body}.$$class == null)`
322
+ first_body_ruby = !`(#{first_body} == null || #{first_body}.$$class == null)`
309
323
  raw = nil
310
324
  if body.is_a?(::Cloudflare::RawResponse)
311
325
  raw = body
312
326
  elsif first_body_ruby && first_body.is_a?(::Cloudflare::RawResponse)
313
327
  raw = first_body
314
328
  end
329
+
315
330
  if raw
316
331
  js_resp = raw.js_response
317
332
  return js_resp
@@ -320,7 +335,7 @@ module Rack
320
335
  # Binary body fast-path: pass the JS ReadableStream directly
321
336
  # to Response without touching Opal's String encoding.
322
337
  if body.is_a?(::Cloudflare::BinaryBody) ||
323
- (first_body_ruby && first_body.is_a?(::Cloudflare::BinaryBody))
338
+ (first_body_ruby && first_body.is_a?(::Cloudflare::BinaryBody))
324
339
  bin = body.is_a?(::Cloudflare::BinaryBody) ? body : first_body
325
340
  js_stream = bin.stream
326
341
  ct = bin.content_type
@@ -331,20 +346,16 @@ module Rack
331
346
  vs = v.to_s
332
347
  `#{js_headers}[#{ks}] = #{vs}`
333
348
  end
349
+
334
350
  `#{js_headers}['content-type'] = #{ct}` if ct
335
351
  `#{js_headers}['cache-control'] = #{cc}` if cc
336
- return(
337
- `new Response(#{js_stream}, { status: #{status.to_i}, headers: #{js_headers} })`
338
- )
352
+ return (`new Response(#{js_stream}, { status: #{status.to_i}, headers: #{js_headers} })`)
339
353
  end
340
354
 
341
355
  if body.is_a?(::Cloudflare::EmbeddedBinaryBody) ||
342
- (
343
- first_body_ruby &&
344
- first_body.is_a?(::Cloudflare::EmbeddedBinaryBody)
345
- )
346
- bin =
347
- body.is_a?(::Cloudflare::EmbeddedBinaryBody) ? body : first_body
356
+ (first_body_ruby &&
357
+ first_body.is_a?(::Cloudflare::EmbeddedBinaryBody))
358
+ bin = body.is_a?(::Cloudflare::EmbeddedBinaryBody) ? body : first_body
348
359
  js_stream = bin.stream
349
360
  ct = bin.content_type
350
361
  cc = bin.cache_control
@@ -354,11 +365,10 @@ module Rack
354
365
  vs = v.to_s
355
366
  `#{js_headers}[#{ks}] = #{vs}`
356
367
  end
368
+
357
369
  `#{js_headers}['content-type'] = #{ct}` if ct
358
370
  `#{js_headers}['cache-control'] = #{cc}` if cc
359
- return(
360
- `new Response(#{js_stream}, { status: #{status.to_i}, headers: #{js_headers} })`
361
- )
371
+ return (`new Response(#{js_stream}, { status: #{status.to_i}, headers: #{js_headers} })`)
362
372
  end
363
373
 
364
374
  # Phase 10 — Workers AI streaming: a Cloudflare::AI::Stream wraps
@@ -366,16 +376,17 @@ module Rack
366
376
  # ("data: {json}\n\n"). Pass it straight through so the client
367
377
  # receives the chunks as they arrive.
368
378
  first_body = (body.first if body.respond_to?(:first))
369
- first_body_ruby =
370
- !`(#{first_body} == null || #{first_body}.$$class == null)`
379
+ first_body_ruby = !`(#{first_body} == null || #{first_body}.$$class == null)`
371
380
 
372
381
  stream_obj = nil
373
382
  if body.respond_to?(:sse_stream?) && body.sse_stream?
374
383
  stream_obj = body
375
- elsif first_body_ruby && first_body.respond_to?(:sse_stream?) &&
376
- first_body.sse_stream?
384
+ elsif first_body_ruby &&
385
+ first_body.respond_to?(:sse_stream?) &&
386
+ first_body.sse_stream?
377
387
  stream_obj = first_body
378
388
  end
389
+
379
390
  if stream_obj
380
391
  js_stream = stream_obj.js_stream
381
392
  js_headers = `({})`
@@ -390,6 +401,7 @@ module Rack
390
401
  vs = v.to_s
391
402
  `#{js_headers}[#{ks}] = #{vs}`
392
403
  end
404
+
393
405
  if stream_obj.respond_to?(:response_headers)
394
406
  stream_obj.response_headers.each do |k, v|
395
407
  ks = k.to_s
@@ -404,18 +416,19 @@ module Rack
404
416
  `#{js_headers}['cache-control'] = 'no-cache, no-transform'`
405
417
  `#{js_headers}['x-accel-buffering'] = 'no'`
406
418
  end
407
- return(
408
- `new Response(#{js_stream}, { status: #{status.to_i}, headers: #{js_headers} })`
409
- )
419
+
420
+ return (`new Response(#{js_stream}, { status: #{status.to_i}, headers: #{js_headers} })`)
410
421
  end
411
422
 
412
423
  raw_response = nil
413
424
  if body.respond_to?(:raw_response?) && body.raw_response?
414
425
  raw_response = body
415
- elsif first_body_ruby && first_body.respond_to?(:raw_response?) &&
416
- first_body.raw_response?
426
+ elsif first_body_ruby &&
427
+ first_body.respond_to?(:raw_response?) &&
428
+ first_body.raw_response?
417
429
  raw_response = first_body
418
430
  end
431
+
419
432
  return raw_response.js_response if raw_response
420
433
 
421
434
  chunks = []
@@ -425,50 +438,47 @@ module Rack
425
438
  chunks << body
426
439
  end
427
440
 
428
- raw_chunk =
429
- chunks.find do |chunk|
430
- chunk_ruby = !`(#{chunk} == null || #{chunk}.$$class == null)`
431
- chunk_ruby && chunk.respond_to?(:raw_response?) &&
432
- chunk.raw_response?
433
- end
441
+ raw_chunk = chunks.find do |chunk|
442
+ chunk_ruby = !`(#{chunk} == null || #{chunk}.$$class == null)`
443
+ chunk_ruby &&
444
+ chunk.respond_to?(:raw_response?) &&
445
+ chunk.raw_response?
446
+ end
447
+
434
448
  return raw_chunk.js_response if raw_chunk
435
449
 
436
- binary_chunk =
437
- chunks.find do |chunk|
438
- chunk_ruby = !`(#{chunk} == null || #{chunk}.$$class == null)`
439
- (
440
- chunk_ruby && chunk.respond_to?(:stream) &&
441
- chunk.respond_to?(:content_type)
442
- ) ||
443
- `#{chunk} != null && #{chunk}.stream != null && #{chunk}.content_type != null`
444
- end
450
+ binary_chunk = chunks.find do |chunk|
451
+ chunk_ruby = !`(#{chunk} == null || #{chunk}.$$class == null)`
452
+ (chunk_ruby &&
453
+ chunk.respond_to?(:stream) &&
454
+ chunk.respond_to?(:content_type)) ||
455
+ `#{chunk} != null && #{chunk}.stream != null && #{chunk}.content_type != null`
456
+ end
457
+
445
458
  if binary_chunk
446
- binary_chunk_ruby =
447
- !`(#{binary_chunk} == null || #{binary_chunk}.$$class == null)`
448
- stream =
449
- if binary_chunk_ruby && binary_chunk.respond_to?(:stream)
450
- binary_chunk.stream
451
- else
452
- `#{binary_chunk}.stream`
453
- end
454
- content_type =
455
- if binary_chunk_ruby && binary_chunk.respond_to?(:content_type)
456
- binary_chunk.content_type
457
- else
458
- `#{binary_chunk}.content_type`
459
- end
460
- cache_control =
461
- if binary_chunk_ruby && binary_chunk.respond_to?(:cache_control)
462
- binary_chunk.cache_control
463
- else
464
- `#{binary_chunk}.cache_control`
465
- end
459
+ binary_chunk_ruby = !`(#{binary_chunk} == null || #{binary_chunk}.$$class == null)`
460
+ stream = if binary_chunk_ruby && binary_chunk.respond_to?(:stream)
461
+ binary_chunk.stream
462
+ else
463
+ `#{binary_chunk}.stream`
464
+ end
465
+
466
+ content_type = if binary_chunk_ruby && binary_chunk.respond_to?(:content_type)
467
+ binary_chunk.content_type
468
+ else
469
+ `#{binary_chunk}.content_type`
470
+ end
471
+
472
+ cache_control = if binary_chunk_ruby && binary_chunk.respond_to?(:cache_control)
473
+ binary_chunk.cache_control
474
+ else
475
+ `#{binary_chunk}.cache_control`
476
+ end
477
+
466
478
  body_headers = {}
467
479
  body_headers["content-type"] = content_type if content_type
468
480
  body_headers["cache-control"] = cache_control if cache_control
469
- return(
470
- `new Response(#{stream}, { status: #{status.to_i}, headers: #{Cloudflare.headers_to_js(body_headers)} })`
471
- )
481
+ return (`new Response(#{stream}, { status: #{status.to_i}, headers: #{Cloudflare.headers_to_js(body_headers)} })`)
472
482
  end
473
483
 
474
484
  # Build JS-side headers. Set-Cookie is the one HTTP response
@@ -496,8 +506,7 @@ module Rack
496
506
  # Convert any `{ __multi__: true, values: [...] }` markers into
497
507
  # a real `Headers` object that Workers' `new Response(headers:)`
498
508
  # accepts. Single-valued headers stay as plain string values.
499
- js_headers =
500
- `(function(h) {
509
+ js_headers = `(function(h) {
501
510
  var hasMulti = false;
502
511
  for (var key in h) {
503
512
  if (h[key] && typeof h[key] === 'object' && h[key].__multi__ === true) {
@@ -526,8 +535,7 @@ module Rack
526
535
  has_promise = false
527
536
  chunks.each do |c|
528
537
  `#{js_chunks}.push(#{c})`
529
- has_promise =
530
- true if `#{c} != null && typeof #{c}.then === 'function'`
538
+ has_promise = true if `#{c} != null && typeof #{c}.then === 'function'`
531
539
  end
532
540
 
533
541
  if has_promise
@@ -543,11 +551,12 @@ module Rack
543
551
  # used for 101 WebSocket upgrades where the Workers
544
552
  # runtime's own Response carries runtime-only properties
545
553
  # (`.webSocket`) that a reconstructed Response would lose.
546
- `Promise.all(#{js_chunks}).then(function(resolved) { var bodyToText = function(v) { if (v == null) { return ''; } if (Array.isArray(v)) { var joined = ''; for (var j = 0; j < v.length; j++) { joined += bodyToText(v[j]); } return joined; } if (typeof v === 'string') { return v; } if (v != null && v.$$is_string) { return v.toString(); } try { return JSON.stringify(v); } catch (e) { return String(v); } }; for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r != null && typeof r === 'object' && typeof r['$raw_response?'] === 'function' && typeof r['$js_response'] === 'function') { try { if (r['$raw_response?']()) { return r['$js_response'](); } } catch (_) {} } } for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r != null && r.stream != null && r.content_type != null) { var bh = {}; bh['content-type'] = r.content_type; if (r.cache_control) bh['cache-control'] = r.cache_control; return new Response(r.stream, { status: #{status_int}, headers: bh }); } } if (resolved.length === 1 && resolved[0] != null && Array.isArray(resolved[0]) && resolved[0].length >= 1 && typeof resolved[0][0] === 'number') { var ov = resolved[0]; var ovs = ov[0]|0; var ovh = #{Cloudflare}.$headers_to_js(nil, #{js_headers}); var ovb = ''; if (ov.length >= 3 && ov[1] != null) { ovh = #{Cloudflare}.$headers_to_js(ov[1], #{js_headers}); ovb = bodyToText(ov[2]); } else if (ov.length >= 2) { ovb = bodyToText(ov[ov.length - 1]); } return new Response(ovb, { status: ovs, headers: ovh }); } var parts = []; for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r == null) { parts.push(''); continue; } if (typeof r === 'string') { parts.push(r); continue; } if (r != null && r.$$is_string) { parts.push(r.toString()); continue; } try { parts.push(JSON.stringify(r)); } catch (e) { parts.push(String(r)); } } return new Response(parts.join(''), { status: #{status_int}, headers: #{js_headers} }); })`
554
+ `Promise.all(#{js_chunks}).then(function(resolved) { var bodyToText = function(v) { if (v == null) { return ''; } if (Array.isArray(v)) { var joined = ''; for (var j = 0; j < v.length; j++) { joined += bodyToText(v[j]); } return joined; } if (typeof v === 'string') { return v; } if (v != null && typeof v.value === 'string') { return v.value; } if (v != null && typeof v.$to_s === 'function') { return bodyToText(v.$to_s()); } if (v != null && typeof v.toString === 'function') { var s = v.toString(); if (typeof s === 'string' && s !== '[object Object]') return s; } try { return JSON.stringify(v); } catch (e) { return String(v); } }; for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r != null && typeof r === 'object' && typeof r['$raw_response?'] === 'function' && typeof r['$js_response'] === 'function') { try { if (r['$raw_response?']()) { return r['$js_response'](); } } catch (_) {} } } for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r != null && r.stream != null && r.content_type != null) { var bh = {}; bh['content-type'] = r.content_type; if (r.cache_control) bh['cache-control'] = r.cache_control; return new Response(r.stream, { status: #{status_int}, headers: bh }); } } if (resolved.length === 1 && resolved[0] != null && Array.isArray(resolved[0]) && resolved[0].length >= 1 && typeof resolved[0][0] === 'number') { var ov = resolved[0]; var ovs = ov[0]|0; var ovh = #{Cloudflare}.$headers_to_js(nil, #{js_headers}); var ovb = ''; if (ov.length >= 3 && ov[1] != null) { ovh = #{Cloudflare}.$headers_to_js(ov[1], #{js_headers}); ovb = bodyToText(ov[2]); } else if (ov.length >= 2) { ovb = bodyToText(ov[ov.length - 1]); } return new Response(ovb, { status: ovs, headers: ovh }); } var parts = []; for (var i = 0; i < resolved.length; i++) { parts.push(bodyToText(resolved[i])); } return new Response(parts.join(''), { status: #{status_int}, headers: #{js_headers} }); })`
547
555
  else
548
556
  body_str = ""
549
557
  chunks.each { |c| body_str = body_str + c.to_s }
550
- `new Response(#{body_str}, { status: #{status_int}, headers: #{js_headers} })`
558
+ js_body = `(function bodyToText(v) { if (v == null) return ''; if (typeof v === 'string') return v; if (v != null && typeof v.value === 'string') return v.value; if (v != null && typeof v.$to_s === 'function') return bodyToText(v.$to_s()); if (v != null && typeof v.toString === 'function') { var s = v.toString(); if (typeof s === 'string' && s !== '[object Object]') return s; } return String(v); })(#{body_str})`
559
+ `new Response(#{js_body}, { status: #{status_int}, headers: #{js_headers} })`
551
560
  end
552
561
  end
553
562
  end
@@ -605,8 +614,10 @@ module Cloudflare
605
614
 
606
615
  class D1Error < BindingError
607
616
  end
617
+
608
618
  class KVError < BindingError
609
619
  end
620
+
610
621
  class R2Error < BindingError
611
622
  end
612
623
 
@@ -630,6 +641,7 @@ module Cloudflare
630
641
  out << js_object_to_hash(js_row)
631
642
  i += 1
632
643
  end
644
+
633
645
  out
634
646
  end
635
647
 
@@ -652,9 +664,11 @@ module Cloudflare
652
664
  elsif `typeof #{v} === 'object' && !Array.isArray(#{v}) && !(#{v} instanceof Date)`
653
665
  v = js_object_to_hash(v)
654
666
  end
667
+
655
668
  h[k] = v
656
669
  i += 1
657
670
  end
671
+
658
672
  h
659
673
  end
660
674
 
@@ -670,6 +684,7 @@ module Cloudflare
670
684
  `#{js_headers}[#{ks}] = #{vs}`
671
685
  end
672
686
  end
687
+
673
688
  js_headers
674
689
  end
675
690
 
@@ -755,6 +770,7 @@ module Cloudflare
755
770
  vs = v.to_s
756
771
  `#{js_headers}[#{ks}] = #{vs}`
757
772
  end
773
+
758
774
  `#{js_headers}['content-type'] = #{@content_type}` if @content_type
759
775
  `#{js_headers}['cache-control'] = #{@cache_control}` if @cache_control
760
776
  RawResponse.new(
@@ -859,6 +875,7 @@ module Cloudflare
859
875
  if defined?(::Cloudflare) && ::Cloudflare.js_promise?(result)
860
876
  result = result.__await__
861
877
  end
878
+
862
879
  return result unless result.is_a?(Hash)
863
880
  nested = result["meta"]
864
881
  return result unless nested.is_a?(Hash)
@@ -1039,8 +1056,10 @@ module Cloudflare
1039
1056
  vs = v.to_s
1040
1057
  `#{js_include}.push(#{vs})`
1041
1058
  end
1059
+
1042
1060
  `#{opts}.include = #{js_include}`
1043
1061
  end
1062
+
1044
1063
  `#{js_bucket}.list(#{opts}).then(function(res) { var rows = []; var arr = res && res.objects ? res.objects : []; for (var i = 0; i < arr.length; i++) { var o = arr[i]; var ct = (o.httpMetadata && o.httpMetadata.contentType) || 'application/octet-stream'; var h = new Map(); h.set('key', o.key); h.set('size', o.size|0); h.set('uploaded', o.uploaded ? o.uploaded.toISOString() : null); h.set('content_type', ct); rows.push(h); } return rows; })`
1045
1064
  end
1046
1065
  end
@@ -1055,9 +1074,10 @@ module Cloudflare
1055
1074
 
1056
1075
  def attach!(env, js_env, js_ctx = nil)
1057
1076
  env["cloudflare.env"] = js_env
1058
- env[
1059
- "cloudflare.ctx"
1060
- ] = js_ctx unless `(#{js_ctx} == null || #{js_ctx} === undefined || #{js_ctx} === Opal.nil)`
1077
+ unless `(#{js_ctx} == null || #{js_ctx} === undefined || #{js_ctx} === Opal.nil)`
1078
+ env["cloudflare.ctx"] = js_ctx
1079
+ end
1080
+
1061
1081
  if `(#{js_env} == null || #{js_env} === undefined || #{js_env} === Opal.nil)`
1062
1082
  return env
1063
1083
  end
@@ -1094,6 +1114,7 @@ module Cloudflare
1094
1114
  if `(#{js_binding} == null || #{js_binding} === undefined || #{js_binding} === Opal.nil)`
1095
1115
  return env
1096
1116
  end
1117
+
1097
1118
  return env unless defined?(::Cloudflare::DurableObjectNamespace)
1098
1119
 
1099
1120
  suffix = normalize_binding_name(name)
@@ -1113,11 +1134,11 @@ module Cloudflare
1113
1134
  while i < len
1114
1135
  key = `#{keys}[#{i}]`
1115
1136
  js_binding = `#{js_env}[#{key}]`
1116
- is_do =
1117
- `#{js_binding} != null && typeof #{js_binding}.idFromName === 'function' && typeof #{js_binding}.get === 'function'`
1137
+ is_do = `#{js_binding} != null && typeof #{js_binding}.idFromName === 'function' && typeof #{js_binding}.get === 'function'`
1118
1138
  attach_durable_object!(env, key, js_binding) if is_do
1119
1139
  i += 1
1120
1140
  end
1141
+
1121
1142
  env
1122
1143
  end
1123
1144
 
@@ -1125,6 +1146,7 @@ module Cloudflare
1125
1146
  if `(#{js_binding} == null || #{js_binding} === undefined || #{js_binding} === Opal.nil)`
1126
1147
  return env
1127
1148
  end
1149
+
1128
1150
  return env unless defined?(::Cloudflare::Queue)
1129
1151
 
1130
1152
  suffix = normalize_binding_name(name)
@@ -1137,12 +1159,13 @@ module Cloudflare
1137
1159
 
1138
1160
  js_send_email = `#{js_env} && #{js_env}.SEND_EMAIL`
1139
1161
  if `#{js_send_email} == null || #{js_send_email} === undefined`
1140
- js_send_email =
1141
- `(typeof globalThis !== 'undefined' && globalThis.__OPAL_WORKERS__ && globalThis.__OPAL_WORKERS__.sendEmailBinding) || null`
1162
+ js_send_email = `(typeof globalThis !== 'undefined' && globalThis.__OPAL_WORKERS__ && globalThis.__OPAL_WORKERS__.sendEmailBinding) || null`
1163
+ end
1164
+
1165
+ if `#{js_send_email} != null`
1166
+ env["cloudflare.SEND_EMAIL"] = Email.new(js_send_email)
1142
1167
  end
1143
- env["cloudflare.SEND_EMAIL"] = Email.new(
1144
- js_send_email
1145
- ) if `#{js_send_email} != null`
1168
+
1146
1169
  env
1147
1170
  end
1148
1171
 
@@ -1164,10 +1187,12 @@ module Cloudflare
1164
1187
  if `(#{raw} == null || #{raw} === undefined || #{raw} === Opal.nil)`
1165
1188
  return nil
1166
1189
  end
1190
+
1167
1191
  if defined?(::Cloudflare::AI::Binding) &&
1168
- `(#{raw} != null && #{raw}.$$class === #{::Cloudflare::AI::Binding})`
1192
+ `(#{raw} != null && #{raw}.$$class === #{::Cloudflare::AI::Binding})`
1169
1193
  return raw
1170
1194
  end
1195
+
1171
1196
  if defined?(::Cloudflare::AI::Binding)
1172
1197
  return ::Cloudflare::AI::Binding.new(raw)
1173
1198
  end
@@ -1180,36 +1205,47 @@ module Cloudflare
1180
1205
  def cf_env
1181
1206
  env["cloudflare.env"]
1182
1207
  end
1208
+
1183
1209
  def cf_ctx
1184
1210
  env["cloudflare.ctx"]
1185
1211
  end
1212
+
1186
1213
  def d1
1187
1214
  env["cloudflare.DB"]
1188
1215
  end
1216
+
1189
1217
  def db
1190
1218
  d1
1191
1219
  end
1220
+
1192
1221
  def kv
1193
1222
  env["cloudflare.KV"]
1194
1223
  end
1224
+
1195
1225
  def bucket
1196
1226
  env["cloudflare.BUCKET"]
1197
1227
  end
1228
+
1198
1229
  def ai
1199
1230
  Cloudflare::Bindings.ai(env)
1200
1231
  end
1232
+
1201
1233
  def send_email
1202
1234
  env["cloudflare.SEND_EMAIL"]
1203
1235
  end
1236
+
1204
1237
  def jobs_queue
1205
1238
  env["cloudflare.QUEUE_JOBS"]
1206
1239
  end
1240
+
1207
1241
  def jobs_dlq
1208
1242
  env["cloudflare.QUEUE_JOBS_DLQ"]
1209
1243
  end
1244
+
1210
1245
  def do_counter
1211
1246
  env["cloudflare.DO_COUNTER"]
1212
1247
  end
1248
+
1213
1249
  def cache
1214
1250
  @__homura_cache ||= Cloudflare::Cache.default
1215
1251
  end
@@ -8,8 +8,10 @@ class Tempfile < StringIO
8
8
  end
9
9
 
10
10
  def self.open(*)
11
- raise NotImplementedError,
12
- "Tempfile is stubbed in homura (Workers have no writable FS)"
11
+ raise(
12
+ NotImplementedError,
13
+ "Tempfile is stubbed in homura (Workers have no writable FS)"
14
+ )
13
15
  end
14
16
 
15
17
  def path
@@ -56,9 +56,11 @@ module Tilt
56
56
  end
57
57
 
58
58
  def new(file = nil, line = nil, options = {}, &block)
59
- raise NotImplementedError,
60
- "Tilt template rendering is not available in homura Phase 2 " \
61
- "(stubbed). Return Strings or arrays from your Sinatra handlers."
59
+ raise(
60
+ NotImplementedError,
61
+ "Tilt template rendering is not available in homura Phase 2 " \
62
+ "(stubbed). Return Strings or arrays from your Sinatra handlers."
63
+ )
62
64
  end
63
65
  end
64
66
  end
@@ -7,10 +7,13 @@ module Zlib
7
7
  class GzipFile
8
8
  class Error < Zlib::Error
9
9
  end
10
+
10
11
  class CRCError < Error
11
12
  end
13
+
12
14
  class LengthError < Error
13
15
  end
16
+
14
17
  class NoFooter < Error
15
18
  end
16
19
  end