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
|
@@ -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})
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
295
|
+
app.helpers(Streaming)
|
|
292
296
|
end
|
|
293
297
|
end
|
|
294
298
|
end
|
data/lib/homura/runtime.rb
CHANGED
|
@@ -37,7 +37,8 @@ require "await"
|
|
|
37
37
|
|
|
38
38
|
class HomuraRuntimeIO
|
|
39
39
|
def initialize(channel)
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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 &&
|
|
376
|
-
|
|
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
|
-
|
|
408
|
-
|
|
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 &&
|
|
416
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
|
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
|
-
`
|
|
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
|
-
|
|
1059
|
-
"cloudflare.ctx"
|
|
1060
|
-
|
|
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
|
|
@@ -1071,6 +1091,7 @@ module Cloudflare
|
|
|
1071
1091
|
env["cloudflare.BUCKET"] = R2Bucket.new(js_r2) if `#{js_r2} != null`
|
|
1072
1092
|
env["cloudflare.AI"] = js_ai if `#{js_ai} != null`
|
|
1073
1093
|
|
|
1094
|
+
attach_all_durable_objects!(env, js_env)
|
|
1074
1095
|
attach_durable_object!(env, :counter, `#{js_env} && #{js_env}.COUNTER`)
|
|
1075
1096
|
attach_queue!(
|
|
1076
1097
|
env,
|
|
@@ -1093,6 +1114,7 @@ module Cloudflare
|
|
|
1093
1114
|
if `(#{js_binding} == null || #{js_binding} === undefined || #{js_binding} === Opal.nil)`
|
|
1094
1115
|
return env
|
|
1095
1116
|
end
|
|
1117
|
+
|
|
1096
1118
|
return env unless defined?(::Cloudflare::DurableObjectNamespace)
|
|
1097
1119
|
|
|
1098
1120
|
suffix = normalize_binding_name(name)
|
|
@@ -1100,10 +1122,31 @@ module Cloudflare
|
|
|
1100
1122
|
env
|
|
1101
1123
|
end
|
|
1102
1124
|
|
|
1125
|
+
def attach_all_durable_objects!(env, js_env)
|
|
1126
|
+
return env unless defined?(::Cloudflare::DurableObjectNamespace)
|
|
1127
|
+
if `(#{js_env} == null || #{js_env} === undefined || #{js_env} === Opal.nil)`
|
|
1128
|
+
return env
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
keys = `Object.keys(#{js_env})`
|
|
1132
|
+
i = 0
|
|
1133
|
+
len = `#{keys}.length`
|
|
1134
|
+
while i < len
|
|
1135
|
+
key = `#{keys}[#{i}]`
|
|
1136
|
+
js_binding = `#{js_env}[#{key}]`
|
|
1137
|
+
is_do = `#{js_binding} != null && typeof #{js_binding}.idFromName === 'function' && typeof #{js_binding}.get === 'function'`
|
|
1138
|
+
attach_durable_object!(env, key, js_binding) if is_do
|
|
1139
|
+
i += 1
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
env
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1103
1145
|
def attach_queue!(env, name, js_binding, binding_name)
|
|
1104
1146
|
if `(#{js_binding} == null || #{js_binding} === undefined || #{js_binding} === Opal.nil)`
|
|
1105
1147
|
return env
|
|
1106
1148
|
end
|
|
1149
|
+
|
|
1107
1150
|
return env unless defined?(::Cloudflare::Queue)
|
|
1108
1151
|
|
|
1109
1152
|
suffix = normalize_binding_name(name)
|
|
@@ -1116,12 +1159,13 @@ module Cloudflare
|
|
|
1116
1159
|
|
|
1117
1160
|
js_send_email = `#{js_env} && #{js_env}.SEND_EMAIL`
|
|
1118
1161
|
if `#{js_send_email} == null || #{js_send_email} === undefined`
|
|
1119
|
-
js_send_email =
|
|
1120
|
-
`(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`
|
|
1121
1163
|
end
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1164
|
+
|
|
1165
|
+
if `#{js_send_email} != null`
|
|
1166
|
+
env["cloudflare.SEND_EMAIL"] = Email.new(js_send_email)
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1125
1169
|
env
|
|
1126
1170
|
end
|
|
1127
1171
|
|
|
@@ -1143,10 +1187,12 @@ module Cloudflare
|
|
|
1143
1187
|
if `(#{raw} == null || #{raw} === undefined || #{raw} === Opal.nil)`
|
|
1144
1188
|
return nil
|
|
1145
1189
|
end
|
|
1190
|
+
|
|
1146
1191
|
if defined?(::Cloudflare::AI::Binding) &&
|
|
1147
|
-
|
|
1192
|
+
`(#{raw} != null && #{raw}.$$class === #{::Cloudflare::AI::Binding})`
|
|
1148
1193
|
return raw
|
|
1149
1194
|
end
|
|
1195
|
+
|
|
1150
1196
|
if defined?(::Cloudflare::AI::Binding)
|
|
1151
1197
|
return ::Cloudflare::AI::Binding.new(raw)
|
|
1152
1198
|
end
|
|
@@ -1159,36 +1205,47 @@ module Cloudflare
|
|
|
1159
1205
|
def cf_env
|
|
1160
1206
|
env["cloudflare.env"]
|
|
1161
1207
|
end
|
|
1208
|
+
|
|
1162
1209
|
def cf_ctx
|
|
1163
1210
|
env["cloudflare.ctx"]
|
|
1164
1211
|
end
|
|
1212
|
+
|
|
1165
1213
|
def d1
|
|
1166
1214
|
env["cloudflare.DB"]
|
|
1167
1215
|
end
|
|
1216
|
+
|
|
1168
1217
|
def db
|
|
1169
1218
|
d1
|
|
1170
1219
|
end
|
|
1220
|
+
|
|
1171
1221
|
def kv
|
|
1172
1222
|
env["cloudflare.KV"]
|
|
1173
1223
|
end
|
|
1224
|
+
|
|
1174
1225
|
def bucket
|
|
1175
1226
|
env["cloudflare.BUCKET"]
|
|
1176
1227
|
end
|
|
1228
|
+
|
|
1177
1229
|
def ai
|
|
1178
1230
|
Cloudflare::Bindings.ai(env)
|
|
1179
1231
|
end
|
|
1232
|
+
|
|
1180
1233
|
def send_email
|
|
1181
1234
|
env["cloudflare.SEND_EMAIL"]
|
|
1182
1235
|
end
|
|
1236
|
+
|
|
1183
1237
|
def jobs_queue
|
|
1184
1238
|
env["cloudflare.QUEUE_JOBS"]
|
|
1185
1239
|
end
|
|
1240
|
+
|
|
1186
1241
|
def jobs_dlq
|
|
1187
1242
|
env["cloudflare.QUEUE_JOBS_DLQ"]
|
|
1188
1243
|
end
|
|
1244
|
+
|
|
1189
1245
|
def do_counter
|
|
1190
1246
|
env["cloudflare.DO_COUNTER"]
|
|
1191
1247
|
end
|
|
1248
|
+
|
|
1192
1249
|
def cache
|
|
1193
1250
|
@__homura_cache ||= Cloudflare::Cache.default
|
|
1194
1251
|
end
|
|
@@ -8,8 +8,10 @@ class Tempfile < StringIO
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def self.open(*)
|
|
11
|
-
raise
|
|
12
|
-
|
|
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
|
data/lib/homura_vendor_tilt.rb
CHANGED
|
@@ -56,9 +56,11 @@ module Tilt
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def new(file = nil, line = nil, options = {}, &block)
|
|
59
|
-
raise
|
|
60
|
-
|
|
61
|
-
|
|
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
|