Module: Cloudflare
- Defined in:
- lib/homura/runtime.rb,
lib/homura/runtime/ai.rb,
lib/homura/runtime/http.rb,
lib/homura/runtime/cache.rb,
lib/homura/runtime/email.rb,
lib/homura/runtime/queue.rb,
lib/homura/runtime/stream.rb,
lib/homura/runtime/multipart.rb,
lib/homura/runtime/scheduled.rb,
lib/homura/runtime/durable_object.rb
Overview
backtick_javascript: true
Phase 11A — multipart/form-data receive pipeline.
Why a bespoke parser instead of Rack::Multipart::Parser?
Rack’s parser does work on Opal in principle (strscan is in stdlib), but it relies on Tempfile — which is a stub on Workers since there is no writable filesystem. It also assumes the request body is a true binary ByteString, whereas on Workers we have to cross the JS/Ruby boundary and Opal Strings are JS Strings (UTF-16 code units). The correct way to pass bytes through that boundary is to encode each byte as a single ‘char code 0-255` latin1 character, then `unescape / String.fromCharCode.apply` back into a Uint8Array when we need raw bytes again (e.g. R2.put).
This module exposes:
Cloudflare::Multipart.parse(body_binstr, content_type)
→ Hash[String => Cloudflare::UploadedFile | String]
Cloudflare::UploadedFile
#filename — original filename from the Content-Disposition header
#content_type — part Content-Type (defaults to application/octet-stream)
#name — form field name
#size — byte length
#bytes_binstr — the latin1-encoded byte string (1 char = 1 byte)
#to_uint8_array — JS Uint8Array suitable for fetch body / R2.put
#read — returns bytes_binstr, mirroring Tempfile#read
#rewind — no-op (content is fully in-memory, there is no
writable filesystem on Workers)
Also installs ‘Rack::Request#post?`-path hook: when the Sinatra route calls `params`, `Rack::Request#POST` delegates to `Cloudflare::Multipart.rack_params(env)` which parses once, caches on the env, and hydrates `params` with UploadedFile / String values.
Defined Under Namespace
Modules: AI, BindingHelpers, Bindings, DurableObject, HTTP, Multipart, QueueConsumer, Scheduled Classes: AIError, BinaryBody, BindingError, Cache, CacheError, D1Database, D1Error, D1Statement, DurableObjectError, DurableObjectId, DurableObjectNamespace, DurableObjectRequest, DurableObjectRequestContext, DurableObjectState, DurableObjectStorage, DurableObjectStub, Email, EmbeddedBinaryBody, HTTPError, HTTPResponse, KVError, KVNamespace, Queue, QueueBatch, QueueContext, QueueError, QueueMessage, R2Bucket, R2Error, RawResponse, SSEOut, SSEStream, ScheduledEvent, UploadedFile
Class Method Summary collapse
- .headers_to_js(headers, fallback = nil) ⇒ Object
-
.js_object_to_hash(js_obj) ⇒ Object
Deep copy of a JS object’s own enumerable string keys into a Hash.
-
.js_promise?(obj) ⇒ Boolean
Check whether the argument is a native JS Promise / thenable.
-
.js_rows_to_ruby(js_rows) ⇒ Object
JS Array -> Ruby Array of Ruby Hashes (for D1 result.results).
-
.js_to_ruby(js_val) ⇒ Object
Generic JS->Ruby for the common Workers AI response shape: { response: “…”, usage: { prompt_tokens: … } } Recursively converts nested objects + arrays.
Class Method Details
.headers_to_js(headers, fallback = nil) ⇒ Object
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 |
# File 'lib/homura/runtime.rb', line 675 def self.headers_to_js(headers, fallback = nil) return fallback || `({})` if headers.nil? return headers if `#{headers} != null && #{headers}.constructor === Object` js_headers = fallback || `({})` if headers.respond_to?(:each) headers.each do |key, value| ks = key.to_s vs = value.to_s `#{js_headers}[#{ks}] = #{vs}` end end js_headers end |
.js_object_to_hash(js_obj) ⇒ Object
Deep copy of a JS object’s own enumerable string keys into a Hash. Recursively converts nested plain objects so Opal code can call Ruby methods (e.g. #is_a?) on every value.
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 |
# File 'lib/homura/runtime.rb', line 651 def self.js_object_to_hash(js_obj) h = {} return h if `#{js_obj} == null` keys = `Object.keys(#{js_obj})` len = `#{keys}.length` i = 0 while i < len k = `#{keys}[#{i}]` v = `#{js_obj}[#{k}]` # Normalize bare JS null/undefined to Ruby nil before storing them. if `#{v} == null` v = nil # Recurse for nested plain objects (but not Arrays, Dates, etc.) elsif `typeof #{v} === 'object' && !Array.isArray(#{v}) && !(#{v} instanceof Date)` v = js_object_to_hash(v) end h[k] = v i += 1 end h end |
.js_promise?(obj) ⇒ Boolean
Check whether the argument is a native JS Promise / thenable. Ruby’s ‘Object#then` (alias of `yield_self`) is a universal method since Ruby 2.6, so `obj.respond_to?(:then)` is always true and is useless as a Promise detector. We must check for a JS function at `.then` instead.
629 630 631 |
# File 'lib/homura/runtime.rb', line 629 def self.js_promise?(obj) `(#{obj} != null && typeof #{obj}.then === 'function' && typeof #{obj}.catch === 'function')` end |
.js_rows_to_ruby(js_rows) ⇒ Object
JS Array -> Ruby Array of Ruby Hashes (for D1 result.results).
634 635 636 637 638 639 640 641 642 643 644 645 646 |
# File 'lib/homura/runtime.rb', line 634 def self.js_rows_to_ruby(js_rows) out = [] return out if `#{js_rows} == null` len = `#{js_rows}.length` i = 0 while i < len js_row = `#{js_rows}[#{i}]` out << js_object_to_hash(js_row) i += 1 end out end |
.js_to_ruby(js_val) ⇒ Object
Generic JS->Ruby for the common Workers AI response shape:
{ response: "...", usage: { prompt_tokens: ... } }
Recursively converts nested objects + arrays.
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 |
# File 'lib/homura/runtime/ai.rb', line 499 def self.js_to_ruby(js_val) return nil if `#{js_val} == null` if `typeof #{js_val} === 'string' || typeof #{js_val} === 'number' || typeof #{js_val} === 'boolean'` return js_val end if `Array.isArray(#{js_val})` out = [] len = `#{js_val}.length` i = 0 while i < len out << js_to_ruby(`#{js_val}[#{i}]`) i += 1 end return out end if `typeof #{js_val} === 'object'` h = {} keys = `Object.keys(#{js_val})` len = `#{keys}.length` i = 0 while i < len k = `#{keys}[#{i}]` h[k] = js_to_ruby(`#{js_val}[#{k}]`) i += 1 end return h end js_val end |