homura-runtime 0.1.0

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.
data/exe/compile-erb ADDED
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # homura ERB precompiler.
5
+ #
6
+ # Opal-on-Workers can't run ERB the normal way: ERB compiles its
7
+ # templates to Ruby source and runs them via `binding.eval`, which
8
+ # lands on `new Function($code)`, which Cloudflare Workers bans with
9
+ # "Code generation from strings disallowed for this context".
10
+ #
11
+ # The workaround is to compile each ERB template to a *plain Ruby
12
+ # method* ahead of time (in CRuby, at build time), then feed that
13
+ # generated Ruby source into Opal alongside everything else. The
14
+ # runtime cost is a single Proc lookup plus a couple of string
15
+ # concatenations per tag; no eval, no new Function, no surprises.
16
+ #
17
+ # Usage:
18
+ # bin/compile-erb --input views --output build/homura_templates.rb --namespace HomuraTemplates
19
+ # or (legacy):
20
+ # bin/compile-erb views/foo.erb # writes compiled templates + Sinatra hook to stdout
21
+ #
22
+ # The output registers each template with `HomuraTemplates` and
23
+ # monkey-patches `Sinatra::Templates#erb` to dispatch there.
24
+
25
+ require 'fileutils'
26
+ require 'optparse'
27
+
28
+ HELP = <<~USAGE
29
+ Usage:
30
+ bin/compile-erb [--input DIR] [--output FILE] [--namespace ModuleName]
31
+ [--stdout] [--] [template.erb ...]
32
+
33
+ Defaults (Phase 15-A homura):
34
+ --input views --output build/homura_templates.rb --namespace HomuraTemplates
35
+
36
+ With positional .erb paths and no --output: print combined Ruby to stdout (legacy).
37
+ USAGE
38
+
39
+ # Very small ERB dialect tokenizer. ERB is simple enough to parse by
40
+ # hand and we specifically DO NOT want to pull in stdlib ERB — its
41
+ # generated source uses `String#<<` mutation, which Opal's immutable
42
+ # Strings reject at runtime.
43
+ module HomuraERB
44
+ module_function
45
+
46
+ # Compile an ERB source string to a Ruby method body that assembles
47
+ # an HTML string in a local variable `_out`. The body references
48
+ # `@ivars` and method calls directly, so it must be run via
49
+ # `instance_exec` on the Sinatra instance the route was dispatched
50
+ # on. That gives `<%= @name %>` the usual Sinatra semantics.
51
+ def compile(source)
52
+ ruby = +"_out = ''\n"
53
+ cursor = 0
54
+ while cursor < source.length
55
+ open_idx = source.index('<%', cursor)
56
+ if open_idx.nil?
57
+ static = source[cursor..]
58
+ ruby << "_out = _out + #{static.inspect}\n" unless static.empty?
59
+ break
60
+ end
61
+
62
+ # Static text before the next `<%`
63
+ if open_idx > cursor
64
+ static = source[cursor...open_idx]
65
+ ruby << "_out = _out + #{static.inspect}\n"
66
+ end
67
+
68
+ close_idx = source.index('%>', open_idx + 2)
69
+ raise "Unmatched `<%` in ERB source (starting at #{open_idx})" if close_idx.nil?
70
+
71
+ inner = source[(open_idx + 2)...close_idx]
72
+
73
+ if inner.start_with?('#')
74
+ # `<%# comment %>` — drop
75
+ elsif inner.start_with?('==')
76
+ # `<%== expression %>` — identical to `<%= %>` in this minimal
77
+ # dialect (no HTML escaping yet; the author is responsible).
78
+ expr = inner[2..].strip
79
+ ruby << "_out = _out + ((#{expr})).to_s\n"
80
+ elsif inner.start_with?('=')
81
+ # `<%= expression %>`
82
+ expr = inner[1..].strip
83
+ ruby << "_out = _out + ((#{expr})).to_s\n"
84
+ else
85
+ # `<% code %>` — Ruby statement(s), emitted verbatim
86
+ ruby << inner.strip << "\n"
87
+ end
88
+
89
+ cursor = close_idx + 2
90
+ end
91
+ ruby << "_out\n"
92
+ ruby
93
+ end
94
+
95
+ # Wrap the compiled body in a `#{namespace}.register` call.
96
+ def wrap(template_name, compiled_body, namespace)
97
+ indented = compiled_body.each_line.map { |l| " #{l}" }.join
98
+ <<~RUBY
99
+ #{namespace}.register(:#{template_name}) do |locals = {}|
100
+ #{indented.chomp}
101
+ end
102
+ RUBY
103
+ end
104
+ end
105
+
106
+ # ---------------------------------------------------------------------------
107
+ # Driver
108
+ # ---------------------------------------------------------------------------
109
+
110
+ def default_inputs_for(dir)
111
+ Dir.glob(File.join(dir, '*.erb')).sort
112
+ end
113
+
114
+ def template_name_for(path)
115
+ File.basename(path, '.erb').to_sym
116
+ end
117
+
118
+ def emit_header(io, namespace)
119
+ io.puts <<~RUBY
120
+ # frozen_string_literal: true
121
+ #
122
+ # Auto-generated by bin/compile-erb — DO NOT EDIT BY HAND.
123
+ #
124
+ # Run `bin/compile-erb` after modifying anything under views/ to
125
+ # regenerate. The Opal build command picks this file up via
126
+ # `-I build -r homura_templates` (or your chosen `--output` basename).
127
+ #
128
+ # Each registered template is a Proc that returns an HTML String.
129
+ # The Proc is `instance_exec`'d on the current Sinatra instance so
130
+ # `@ivars`, `params`, `request`, and any Sinatra helper remain
131
+ # accessible exactly as they would in a stock Sinatra route.
132
+ #
133
+ # Rendering is wired to `Sinatra::Templates#erb` further down so
134
+ # `erb :index` in user code dispatches here transparently. No
135
+ # runtime eval, no `new Function`, no Workers sandbox complaints.
136
+
137
+ module #{namespace}
138
+ @templates = {}
139
+
140
+ class << self
141
+ def register(name, &body)
142
+ @templates[name.to_sym] = body
143
+ end
144
+
145
+ def render(name, instance, locals = {})
146
+ body = @templates[name.to_sym]
147
+ raise "#{namespace}: no template registered for \#{name.inspect}" unless body
148
+ instance.instance_exec(locals, &body)
149
+ end
150
+
151
+ def registered?(name)
152
+ @templates.key?(name.to_sym)
153
+ end
154
+
155
+ def names
156
+ @templates.keys
157
+ end
158
+ end
159
+ end
160
+ RUBY
161
+ end
162
+
163
+ def emit_templates(io, inputs, namespace)
164
+ inputs.each do |path|
165
+ name = template_name_for(path)
166
+ source = File.read(path)
167
+ compiled = HomuraERB.compile(source)
168
+ io.puts
169
+ io.puts "# ---- #{path} -> :#{name} --------------------------------------"
170
+ io.puts HomuraERB.wrap(name, compiled, namespace)
171
+ end
172
+ end
173
+
174
+ def emit_sinatra_patch(io, namespace)
175
+ io.puts <<~RUBY
176
+
177
+ # ---------------------------------------------------------------------
178
+ # Sinatra hook: `erb :name` dispatches to #{namespace}.render.
179
+ # ---------------------------------------------------------------------
180
+ #
181
+ # This file is loaded by Opal via `-r homura_templates` before
182
+ # `app/hello.rb`, so Sinatra is not yet loaded at require time.
183
+ # Pull it in explicitly and reopen `Sinatra::Templates` to install
184
+ # our ERB dispatcher. User code's own `require 'sinatra/base'`
185
+ # later becomes a no-op (already cached).
186
+ require 'sinatra/base'
187
+
188
+ module ::Sinatra
189
+ module Templates
190
+ # homura patch: dispatch to precompiled templates when we
191
+ # have one. Unknown symbols raise a clear message instead of
192
+ # wandering into upstream Tilt, which would blow up on
193
+ # Workers with "Code generation from strings disallowed".
194
+ def erb(template, options = {}, locals = {}, &block)
195
+ if template.is_a?(::Symbol) && ::#{namespace}.registered?(template)
196
+ locals ||= {}
197
+ ::#{namespace}.render(template, self, locals)
198
+ else
199
+ raise "homura: erb \#{template.inspect} not precompiled; run bin/compile-erb"
200
+ end
201
+ end
202
+ end
203
+ end
204
+ RUBY
205
+ end
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # Entry point
209
+ # ---------------------------------------------------------------------------
210
+
211
+ if ARGV.include?('-h') || ARGV.include?('--help')
212
+ puts HELP
213
+ exit 0
214
+ end
215
+
216
+ options = {
217
+ input_dir: 'views',
218
+ output: 'build/homura_templates.rb',
219
+ namespace: 'HomuraTemplates',
220
+ stdout: false
221
+ }
222
+
223
+ parser = OptionParser.new do |op|
224
+ op.banner = 'Usage: bin/compile-erb [options] [--] [files...]'
225
+ op.on('--input DIR', 'Directory containing *.erb templates') { |d| options[:input_dir] = d }
226
+ op.on('--output PATH', 'Write generated Ruby to PATH') { |p| options[:output] = p }
227
+ op.on('--namespace NAME', 'Ruby module name for the registry') { |n| options[:namespace] = n }
228
+ op.on('--stdout', 'Write generated Ruby to stdout') { options[:stdout] = true }
229
+ end
230
+ parser.parse!
231
+
232
+ positional = ARGV.dup
233
+ namespace = options[:namespace]
234
+
235
+ inputs =
236
+ if positional.any?
237
+ positional.flat_map { |a| File.directory?(a) ? Dir.glob(File.join(a, '*.erb')).sort : [a] }
238
+ else
239
+ default_inputs_for(options[:input_dir])
240
+ end
241
+
242
+ if inputs.empty?
243
+ warn "bin/compile-erb: no .erb files found (under #{options[:input_dir]}/ or from args)"
244
+ exit 1
245
+ end
246
+
247
+ write_file = !options[:stdout] && (positional.empty? || options[:output])
248
+ out_path = options[:output]
249
+
250
+ if write_file
251
+ FileUtils.mkdir_p(File.dirname(out_path))
252
+ File.open(out_path, 'w') do |io|
253
+ emit_header(io, namespace)
254
+ emit_templates(io, inputs, namespace)
255
+ emit_sinatra_patch(io, namespace)
256
+ end
257
+ warn "compile-erb: wrote #{out_path} (#{inputs.size} templates)"
258
+ else
259
+ emit_header($stdout, namespace)
260
+ emit_templates($stdout, inputs, namespace)
261
+ emit_sinatra_patch($stdout, namespace)
262
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+ # backtick_javascript: true
3
+ # await: true
4
+ #
5
+ # Phase 10 — Workers AI binding wrapper.
6
+ #
7
+ # `Cloudflare::AI.run(model, inputs, binding: env['cloudflare.AI'])`
8
+ # wraps `env.AI.run(model, inputs, options)` and returns a Ruby Hash so
9
+ # Sinatra routes can call:
10
+ #
11
+ # ai = env['cloudflare.AI']
12
+ # out = Cloudflare::AI.run(
13
+ # '@cf/google/gemma-4-26b-a4b-it',
14
+ # { messages: [
15
+ # { role: 'system', content: 'You are a helpful assistant.' },
16
+ # { role: 'user', content: 'こんにちは' }
17
+ # ] },
18
+ # binding: ai
19
+ # ).__await__
20
+ # out['response'] # => "..."
21
+ #
22
+ # Streaming (`stream: true`) returns the raw JS ReadableStream wrapped
23
+ # in `Cloudflare::AI::Stream` so a route can hand it to a Server-Sent
24
+ # Events response. See `lib/cloudflare_workers.rb#build_js_response`
25
+ # for the SSE / ReadableStream pass-through.
26
+
27
+ require 'json'
28
+
29
+ module Cloudflare
30
+ class AIError < StandardError
31
+ attr_reader :model, :operation
32
+ def initialize(message, model: nil, operation: nil)
33
+ @model = model
34
+ @operation = operation
35
+ super("[Cloudflare::AI] model=#{model || '?'} op=#{operation || 'run'}: #{message}")
36
+ end
37
+ end
38
+
39
+ module AI
40
+ # Default REST options forwarded to env.AI.run as the third argument.
41
+ DEFAULT_OPTIONS = {}.freeze
42
+
43
+ # Run a Workers AI model. Returns a JS Promise that resolves to a
44
+ # Ruby Hash for non-streaming calls, or to a Cloudflare::AI::Stream
45
+ # wrapping the JS ReadableStream for streaming calls.
46
+ #
47
+ # @param model [String] catalog model id, e.g. '@cf/google/gemma-4-26b-a4b-it'
48
+ # @param inputs [Hash] model inputs (messages / prompt / max_tokens / etc.)
49
+ # @param binding [JS object] env.AI binding (required)
50
+ # @param options [Hash] gateway / extra options forwarded as the 3rd arg
51
+ def self.run(model, inputs, binding: nil, options: nil)
52
+ # Use a JS-side null check because `binding` may be a raw JS object
53
+ # (env.AI), which has no Ruby `#nil?` method on the prototype.
54
+ bound = !`(#{binding} == null)`
55
+ raise AIError.new('AI binding not bound (env.AI is null)', model: model) unless bound
56
+ js_inputs = ruby_to_js(inputs)
57
+ js_options = options ? ruby_to_js(options) : `({})`
58
+ ai_binding = binding
59
+ err_klass = Cloudflare::AIError
60
+ stream_klass = Cloudflare::AI::Stream
61
+ # Streaming may be requested either via `inputs[:stream]` (the
62
+ # newer Workers AI shape) or `options: { stream: true }` (the
63
+ # 3rd-arg "options" contract). Accept both so callers can use
64
+ # whichever idiom matches the model docs they're following.
65
+ streaming = (inputs.is_a?(Hash) && (inputs[:stream] == true || inputs['stream'] == true)) ||
66
+ (options.is_a?(Hash) && (options[:stream] == true || options['stream'] == true))
67
+ cf = Cloudflare
68
+
69
+ # NOTE: multi-line backtick → Promise works HERE because the
70
+ # value is assigned to `js_promise` (Opal emits the statement AND
71
+ # keeps the returned value alive through the local). Do NOT
72
+ # refactor this so the backtick is the method's last expression
73
+ # or the Promise will be silently dropped (same pitfall
74
+ # documented in lib/cloudflare_workers/{cache,queue}.rb —
75
+ # Phase 11B audit).
76
+ js_promise = `
77
+ (async function() {
78
+ var out;
79
+ try {
80
+ out = await #{ai_binding}.run(#{model}, #{js_inputs}, #{js_options});
81
+ } catch (e) {
82
+ #{Kernel}.$raise(#{err_klass}.$new(e && e.message ? e.message : String(e), Opal.hash({ model: #{model}, operation: 'run' })));
83
+ }
84
+ return out;
85
+ })()
86
+ `
87
+
88
+ js_result = js_promise.__await__
89
+
90
+ if streaming
91
+ # Workers AI returns a ReadableStream<Uint8Array> when stream:true.
92
+ # Wrap it so the Sinatra route can return it as an SSE body.
93
+ stream_klass.new(js_result)
94
+ else
95
+ cf.js_to_ruby(js_result)
96
+ end
97
+ end
98
+
99
+ # Convert a Ruby value (Hash / Array / String / Numeric / true / false / nil)
100
+ # into a plain JS object suitable for env.AI.run inputs.
101
+ def self.ruby_to_js(val)
102
+ if val.is_a?(Hash)
103
+ obj = `({})`
104
+ val.each do |k, v|
105
+ ks = k.to_s
106
+ jv = ruby_to_js(v)
107
+ `#{obj}[#{ks}] = #{jv}`
108
+ end
109
+ obj
110
+ elsif val.is_a?(Array)
111
+ arr = `([])`
112
+ val.each do |v|
113
+ jv = ruby_to_js(v)
114
+ `#{arr}.push(#{jv})`
115
+ end
116
+ arr
117
+ elsif val.is_a?(Symbol)
118
+ val.to_s
119
+ else
120
+ val
121
+ end
122
+ end
123
+
124
+ # Streaming wrapper. Holds the raw JS ReadableStream<Uint8Array>
125
+ # returned by env.AI.run when `stream: true` is set. Sinatra routes
126
+ # return this from a route body and `build_js_response` recognises
127
+ # it via duck-typing (`#sse_stream?`) to pass the stream straight
128
+ # into `new Response(stream, …)`.
129
+ #
130
+ # Phase 11A: unified with Cloudflare::SSEStream so both stream
131
+ # types go through the same `response_headers` path in
132
+ # `build_js_response`. The AI::Stream wraps a pre-existing JS
133
+ # ReadableStream (produced by env.AI.run), whereas SSEStream
134
+ # produces its own. The adapter doesn't need to care — it just
135
+ # calls `#js_stream` and `#response_headers`.
136
+ class Stream
137
+ attr_reader :js_stream
138
+
139
+ def initialize(js_stream, headers: nil)
140
+ @js_stream = js_stream
141
+ @extra_headers = headers || {}
142
+ end
143
+
144
+ def sse_stream?
145
+ true
146
+ end
147
+
148
+ # Merged SSE headers — same shape as SSEStream#response_headers,
149
+ # so build_js_response can pass the stream through without a
150
+ # special AI branch. Reference Cloudflare::SSEStream lazily so
151
+ # this file still loads if stream.rb hasn't been required yet
152
+ # (Phase 11A load-order flip: ai.rb currently loads first).
153
+ def response_headers
154
+ defaults = defined?(::Cloudflare::SSEStream) ?
155
+ ::Cloudflare::SSEStream::DEFAULT_HEADERS :
156
+ { 'content-type' => 'text/event-stream; charset=utf-8',
157
+ 'cache-control' => 'no-cache, no-transform',
158
+ 'x-accel-buffering' => 'no' }
159
+ defaults.merge(@extra_headers)
160
+ end
161
+
162
+ def each; end
163
+ def close; end
164
+ end
165
+ end
166
+
167
+ # Generic JS->Ruby for the common Workers AI response shape:
168
+ # { response: "...", usage: { prompt_tokens: ... } }
169
+ # Recursively converts nested objects + arrays.
170
+ def self.js_to_ruby(js_val)
171
+ return nil if `#{js_val} == null`
172
+ return js_val if `typeof #{js_val} === 'string' || typeof #{js_val} === 'number' || typeof #{js_val} === 'boolean'`
173
+ if `Array.isArray(#{js_val})`
174
+ out = []
175
+ len = `#{js_val}.length`
176
+ i = 0
177
+ while i < len
178
+ out << js_to_ruby(`#{js_val}[#{i}]`)
179
+ i += 1
180
+ end
181
+ return out
182
+ end
183
+ if `typeof #{js_val} === 'object'`
184
+ h = {}
185
+ keys = `Object.keys(#{js_val})`
186
+ len = `#{keys}.length`
187
+ i = 0
188
+ while i < len
189
+ k = `#{keys}[#{i}]`
190
+ h[k] = js_to_ruby(`#{js_val}[#{k}]`)
191
+ i += 1
192
+ end
193
+ return h
194
+ end
195
+ js_val
196
+ end
197
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module CloudflareWorkers
6
+ class AsyncRegistry
7
+ class Builder
8
+ def initialize(registry)
9
+ @registry = registry
10
+ end
11
+
12
+ def async_class(class_name, except: [:new])
13
+ @registry.async_classes[class_name] = except.to_set
14
+ end
15
+
16
+ def async_method(class_name, method_name)
17
+ (@registry.async_methods[class_name] ||= Set.new) << method_name
18
+ end
19
+
20
+ def async_factory(class_name, method_name)
21
+ (@registry.async_factories[class_name] ||= Set.new) << method_name
22
+ end
23
+
24
+ def taint_return(class_name, method_name, return_class_name)
25
+ (@registry.taint_returns[class_name] ||= {})[method_name] = return_class_name
26
+ end
27
+
28
+ def async_accessor(lvar_name, accessor_name, class_name)
29
+ @registry.async_accessors[[lvar_name.to_sym, accessor_name.to_sym]] = class_name
30
+ end
31
+
32
+ def async_helper(method_name, class_name)
33
+ (@registry.async_helpers[method_name.to_sym] ||= Set.new) << class_name
34
+ end
35
+
36
+ def helper_factory(method_name, class_name)
37
+ @registry.helper_factories[method_name.to_sym] = class_name
38
+ end
39
+ end
40
+
41
+ class << self
42
+ def register_async_source(&block)
43
+ builder = Builder.new(instance)
44
+ builder.instance_eval(&block)
45
+ end
46
+
47
+ def instance
48
+ @instance ||= new
49
+ end
50
+
51
+ def reset!
52
+ @instance = new
53
+ end
54
+
55
+ def async?(class_name, method_name)
56
+ instance.async?(class_name, method_name)
57
+ end
58
+
59
+ def factory?(class_name, method_name)
60
+ instance.factory?(class_name, method_name)
61
+ end
62
+
63
+ def taint_return_class(class_name, method_name)
64
+ instance.taint_return_class(class_name, method_name)
65
+ end
66
+
67
+ def tainted_class?(class_name)
68
+ instance.tainted_class?(class_name)
69
+ end
70
+
71
+ def auto_load_gem_async_sources(debug: false)
72
+ return unless defined?(Gem) && Gem.respond_to?(:loaded_specs)
73
+
74
+ loaded = 0
75
+ Gem.loaded_specs.each_value do |spec|
76
+ next if spec.full_gem_path.nil?
77
+
78
+ lib_dir = File.join(spec.full_gem_path, 'lib')
79
+ next unless Dir.exist?(lib_dir)
80
+
81
+ Dir.glob(File.join(lib_dir, '**', '*.rb')).each do |path|
82
+ next unless File.read(path, 8192).include?('register_async_source')
83
+
84
+ require_path = path.sub(Regexp.new("^#{Regexp.escape(lib_dir)}/"), '').sub(/\.rb\z/, '')
85
+ begin
86
+ require require_path
87
+ loaded += 1
88
+ puts "[auto-await] loaded async source from #{spec.name}: #{require_path}" if debug
89
+ rescue LoadError, StandardError => e
90
+ warn "[auto-await] Warning: failed to load async source from #{spec.name}/#{require_path}: #{e.message}" if debug
91
+ end
92
+ end
93
+ end
94
+
95
+ puts "[auto-await] auto-loaded #{loaded} async source file(s)" if debug && loaded.positive?
96
+ end
97
+ end
98
+
99
+ attr_reader :async_classes, :async_methods, :async_factories, :taint_returns, :async_accessors, :async_helpers, :helper_factories
100
+
101
+ def initialize
102
+ @async_classes = {}
103
+ @async_methods = {}
104
+ @async_factories = {}
105
+ @taint_returns = {}
106
+ @async_accessors = {}
107
+ @async_helpers = {}
108
+ @helper_factories = {}
109
+ end
110
+
111
+ def async?(class_name, method_name)
112
+ return false if method_name == :new
113
+ methods = @async_methods[class_name]
114
+ return true if methods&.include?(method_name)
115
+ except = @async_classes[class_name]
116
+ return true if except && !except.include?(method_name.to_s) && !except.include?(method_name.to_sym)
117
+ false
118
+ end
119
+
120
+ def factory?(class_name, method_name)
121
+ @async_factories[class_name]&.include?(method_name)
122
+ end
123
+
124
+ def taint_return_class(class_name, method_name)
125
+ @taint_returns[class_name]&.[](method_name)
126
+ end
127
+
128
+ def tainted_class?(class_name)
129
+ @async_classes.key?(class_name) ||
130
+ @async_methods.key?(class_name) ||
131
+ @async_factories.key?(class_name) ||
132
+ @taint_returns.key?(class_name)
133
+ end
134
+ end
135
+ end
136
+
137
+ # Phase 17.5 — Auto-Await: register runtime gem async sources.
138
+ # Each binding declares which methods return Promises so the
139
+ # build-time analyzer can insert .__await__ automatically.
140
+ CloudflareWorkers::AsyncRegistry.register_async_source do
141
+ async_method 'Cloudflare::D1Database', :execute
142
+ async_method 'Cloudflare::D1Database', :get_first_row
143
+ async_method 'Cloudflare::D1Database', :execute_insert
144
+ async_method 'Cloudflare::D1Database', :execute_batch
145
+ taint_return 'Cloudflare::D1Database', :prepare, 'Cloudflare::D1Statement'
146
+ taint_return 'Cloudflare::D1Database', :[], 'Cloudflare::D1Statement'
147
+
148
+ async_method 'Cloudflare::D1Statement', :all
149
+ async_method 'Cloudflare::D1Statement', :first
150
+ async_method 'Cloudflare::D1Statement', :run
151
+
152
+ async_method 'Cloudflare::KVNamespace', :get
153
+ async_method 'Cloudflare::KVNamespace', :get_with_metadata
154
+ async_method 'Cloudflare::KVNamespace', :put
155
+ async_method 'Cloudflare::KVNamespace', :delete
156
+ async_method 'Cloudflare::KVNamespace', :list
157
+
158
+ async_method 'Cloudflare::R2Bucket', :get
159
+ async_method 'Cloudflare::R2Bucket', :get_binary
160
+ async_method 'Cloudflare::R2Bucket', :put
161
+ async_method 'Cloudflare::R2Bucket', :delete
162
+ async_method 'Cloudflare::R2Bucket', :list
163
+ async_method 'Cloudflare::R2Bucket', :head
164
+
165
+ async_method 'Cloudflare::AI', :run
166
+ taint_return 'Cloudflare::AI', :run_stream, 'Cloudflare::AI::Stream'
167
+
168
+ async_method 'Cloudflare::Cache', :match
169
+ async_method 'Cloudflare::Cache', :put
170
+ async_method 'Cloudflare::Cache', :delete
171
+
172
+ async_factory 'Cloudflare::Email', :new
173
+ async_method 'Cloudflare::Email', :send
174
+
175
+ async_method 'Cloudflare::Queue', :send
176
+ async_method 'Cloudflare::Queue', :send_batch
177
+
178
+ async_factory 'Cloudflare::DurableObjectNamespace', :new
179
+ taint_return 'Cloudflare::DurableObjectNamespace', :get, 'Cloudflare::DurableObjectStub'
180
+ taint_return 'Cloudflare::DurableObjectNamespace', :get_by_name, 'Cloudflare::DurableObjectStub'
181
+ taint_return 'Cloudflare::DurableObjectState', :storage, 'Cloudflare::DurableObjectStorage'
182
+ async_method 'Cloudflare::DurableObjectStub', :fetch
183
+
184
+ async_method 'Cloudflare::DurableObjectStorage', :get
185
+ async_method 'Cloudflare::DurableObjectStorage', :put
186
+ async_method 'Cloudflare::DurableObjectStorage', :delete
187
+ async_method 'Cloudflare::DurableObjectStorage', :list
188
+ async_method 'Cloudflare::DurableObjectStorage', :transaction
189
+
190
+ async_method 'Cloudflare::HTTP', :fetch
191
+
192
+ async_method 'Faraday::Connection', :get
193
+ async_method 'Faraday::Connection', :post
194
+ async_method 'Faraday::Connection', :put
195
+ async_method 'Faraday::Connection', :delete
196
+ async_method 'Faraday::Connection', :patch
197
+ async_method 'Faraday::Connection', :head
198
+ end