Module: Cloudflare

Defined in:
lib/cloudflare_workers.rb,
lib/cloudflare_workers/ai.rb,
lib/cloudflare_workers/http.rb,
lib/cloudflare_workers/cache.rb,
lib/cloudflare_workers/email.rb,
lib/cloudflare_workers/queue.rb,
lib/cloudflare_workers/stream.rb,
lib/cloudflare_workers/multipart.rb,
lib/cloudflare_workers/scheduled.rb,
lib/cloudflare_workers/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, DurableObject, HTTP, Multipart, QueueConsumer, Scheduled Classes: AIError, BinaryBody, BindingError, Cache, CacheError, D1Database, D1Error, D1Statement, DurableObjectError, DurableObjectId, DurableObjectNamespace, DurableObjectRequest, DurableObjectRequestContext, DurableObjectState, DurableObjectStorage, DurableObjectStub, Email, HTTPError, HTTPResponse, KVError, KVNamespace, Queue, QueueBatch, QueueContext, QueueError, QueueMessage, R2Bucket, R2Error, RawResponse, SSEOut, SSEStream, ScheduledEvent, UploadedFile

Class Method Summary collapse

Class Method Details

.js_object_to_hash(js_obj) ⇒ Object

Shallow copy of a JS object’s own enumerable string keys into a Hash.



473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/cloudflare_workers.rb', line 473

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}]`
    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.

Returns:

  • (Boolean)


454
455
456
# File 'lib/cloudflare_workers.rb', line 454

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).



459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/cloudflare_workers.rb', line 459

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.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/cloudflare_workers/ai.rb', line 170

def self.js_to_ruby(js_val)
  return nil if `#{js_val} == null`
  return js_val if `typeof #{js_val} === 'string' || typeof #{js_val} === 'number' || typeof #{js_val} === 'boolean'`
  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