homura-runtime 0.1.4 → 0.1.6
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/README.md +1 -1
- data/bin/cloudflare-workers-build +11 -21
- data/exe/auto-await +9 -2
- data/exe/compile-erb +38 -5
- data/lib/cloudflare_workers/build_support.rb +57 -0
- data/lib/cloudflare_workers/version.rb +1 -1
- data/lib/cloudflare_workers.rb +18 -9
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aae4102ce898507966e1a993b16033cf74ede5f2543d17e56d552c6b3ecfabaf
|
|
4
|
+
data.tar.gz: 16b3be88c09f2189193da02cde5904a18749a74edf80e38927a2e47329ec06a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57f457bf20205a3bd68d81d5ad616b971892c513023d61dd9b56f575bc0cf70063b9753810abb1163084ebd48d7ae8439bcfd5c72a53e3dd25bdc3f98148b796
|
|
7
|
+
data.tar.gz: 6c69fb69b6c79a3309a9e5796534023ee47066027a97c809034615635b1df49e284ee0c010fce48059f062d43ec94e12f01ace5b39525b5f6e7d64940a4affa2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.6 (2026-04-23)
|
|
4
|
+
|
|
5
|
+
- Teach `cloudflare-workers-build --standalone --with-db` to add the packaged
|
|
6
|
+
`sequel-d1` gem `vendor/` directory to the Opal load path before the gem's
|
|
7
|
+
`lib/`, so `require 'sequel'` resolves to the bundled Opal-compatible Sequel
|
|
8
|
+
subset instead of the CRuby gem.
|
|
9
|
+
|
|
10
|
+
## 0.1.5 (2026-04-23)
|
|
11
|
+
|
|
12
|
+
- Make `auto-await` emit rewritten files for existing hand-written `.__await__`
|
|
13
|
+
usage when the only missing piece is `# await: true`.
|
|
14
|
+
- Make `cloudflare-workers-build --standalone` restore `cf-runtime/` from the
|
|
15
|
+
packaged gem and derive standalone template/asset namespaces from the project
|
|
16
|
+
name by default, with explicit override flags when needed.
|
|
17
|
+
- Reject unsupported ERB yield forms like `<% yield %>` and `yield(arg)` with
|
|
18
|
+
compile-time guidance toward the supported Sinatra-style layout forms.
|
|
19
|
+
|
|
3
20
|
## 0.1.4 (2026-04-23)
|
|
4
21
|
|
|
5
22
|
- Teach the precompiled ERB runtime to support Sinatra-style layout blocks and
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Core Ruby + Module Worker glue for [Opal](https://opalrb.com/) on [Cloudflare Wo
|
|
|
9
9
|
- `runtime/worker_module.mjs` — fetch / scheduled / queue / DO adapters (**no Opal bundle import**).
|
|
10
10
|
- `runtime/worker.mjs` — thin bootstrap (crypto shim → bundle → `worker_module`) for legacy layouts.
|
|
11
11
|
- `runtime/setup-node-crypto.mjs` — `node:crypto` on `globalThis` before the Opal bundle loads.
|
|
12
|
-
- `bin/cloudflare-workers-build` — single build pipeline (ERB → assets → Opal → patch → `worker.entrypoint.mjs`). Use `--standalone` in generated apps.
|
|
12
|
+
- `bin/cloudflare-workers-build` — single build pipeline (ERB → assets → Opal → patch → `worker.entrypoint.mjs`). Use `--standalone` in generated apps; it now restores `cf-runtime/` automatically and derives standalone template/asset namespaces from the project name by default.
|
|
13
13
|
- `docs/ARCHITECTURE.md` — wrangler `main`, codegen entrypoint, and fixed-import policy.
|
|
14
14
|
|
|
15
15
|
## Quick start (homura monorepo)
|
|
@@ -44,7 +44,9 @@ options = {
|
|
|
44
44
|
bundle_import: nil,
|
|
45
45
|
worker_module_import: nil,
|
|
46
46
|
entrypoint_out: nil,
|
|
47
|
-
with_db: false
|
|
47
|
+
with_db: false,
|
|
48
|
+
templates_namespace: nil,
|
|
49
|
+
assets_namespace: nil
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
OptionParser.new do |o|
|
|
@@ -63,9 +65,13 @@ OptionParser.new do |o|
|
|
|
63
65
|
o.on('--bundle-import PATH', 'Opal bundle import in entrypoint') { |p| options[:bundle_import] = p }
|
|
64
66
|
o.on('--worker-module-import PATH', 'worker_module.mjs import in entrypoint') { |p| options[:worker_module_import] = p }
|
|
65
67
|
o.on('--entrypoint-out PATH', 'Where to write worker.entrypoint.mjs') { |p| options[:entrypoint_out] = p }
|
|
68
|
+
o.on('--templates-namespace NAME', 'Standalone templates module name (default: project-derived)') { |n| options[:templates_namespace] = n }
|
|
69
|
+
o.on('--assets-namespace NAME', 'Standalone assets module name (default: project-derived)') { |n| options[:assets_namespace] = n }
|
|
66
70
|
end.parse!
|
|
67
71
|
|
|
68
72
|
root = Pathname(options[:root]).expand_path
|
|
73
|
+
options[:templates_namespace] ||= CloudflareWorkers::BuildSupport.standalone_namespace(root, 'Templates') if options[:standalone]
|
|
74
|
+
options[:assets_namespace] ||= CloudflareWorkers::BuildSupport.standalone_namespace(root, 'Assets') if options[:standalone]
|
|
69
75
|
|
|
70
76
|
if options[:standalone]
|
|
71
77
|
Dir.chdir(root) { require 'bundler/setup' }
|
|
@@ -115,24 +121,7 @@ def homura_vendor_from_gemfile(project_root)
|
|
|
115
121
|
end
|
|
116
122
|
|
|
117
123
|
def run_opal_standalone!(root, opal_input, opal_output, with_db:)
|
|
118
|
-
load_paths =
|
|
119
|
-
hv = homura_vendor_from_gemfile(root)
|
|
120
|
-
load_paths << hv.to_s if hv
|
|
121
|
-
runtime_name = CloudflareWorkers::BuildSupport::RUNTIME_GEM_NAME
|
|
122
|
-
sinatra_name = CloudflareWorkers::BuildSupport::SINATRA_GEM_NAME
|
|
123
|
-
|
|
124
|
-
load_paths += ['build/auto_await/app', 'app']
|
|
125
|
-
[
|
|
126
|
-
CloudflareWorkersBuild.gem_lib(runtime_name),
|
|
127
|
-
CloudflareWorkersBuild.gem_vendor(runtime_name),
|
|
128
|
-
CloudflareWorkersBuild.gem_lib(sinatra_name),
|
|
129
|
-
CloudflareWorkersBuild.gem_vendor(sinatra_name)
|
|
130
|
-
].compact.each do |path|
|
|
131
|
-
load_paths << path
|
|
132
|
-
end
|
|
133
|
-
load_paths << CloudflareWorkersBuild.gem_lib('sequel-d1') if with_db
|
|
134
|
-
load_paths << 'vendor' if root.join('vendor').directory?
|
|
135
|
-
load_paths << 'build'
|
|
124
|
+
load_paths = CloudflareWorkers::BuildSupport.standalone_load_paths(root, with_db: with_db)
|
|
136
125
|
|
|
137
126
|
argv = ['bundle', 'exec', 'opal', '-c', '-E', '--esm', '--no-source-map']
|
|
138
127
|
load_paths.each { |p| argv.push('-I', p) }
|
|
@@ -199,12 +188,13 @@ unless options[:standalone]
|
|
|
199
188
|
opal_out = root.join(opal_out) unless opal_out.absolute?
|
|
200
189
|
run_opal_homura!(root, opal_in.relative_path_from(root).to_s, opal_out.relative_path_from(root).to_s)
|
|
201
190
|
else
|
|
191
|
+
CloudflareWorkers::BuildSupport.ensure_standalone_runtime(root, current_file: __FILE__)
|
|
202
192
|
run!(
|
|
203
193
|
[
|
|
204
194
|
'ruby', CloudflareWorkersBuild.exe_path('compile-erb').to_s,
|
|
205
195
|
'--input', 'views',
|
|
206
196
|
'--output', 'build/app_templates.rb',
|
|
207
|
-
'--namespace',
|
|
197
|
+
'--namespace', options[:templates_namespace]
|
|
208
198
|
],
|
|
209
199
|
chdir: root
|
|
210
200
|
)
|
|
@@ -213,7 +203,7 @@ else
|
|
|
213
203
|
'ruby', CloudflareWorkersBuild.exe_path('compile-assets').to_s,
|
|
214
204
|
'--input', 'public',
|
|
215
205
|
'--output', 'build/app_assets.rb',
|
|
216
|
-
'--namespace',
|
|
206
|
+
'--namespace', options[:assets_namespace]
|
|
217
207
|
],
|
|
218
208
|
chdir: root
|
|
219
209
|
)
|
data/exe/auto-await
CHANGED
|
@@ -72,18 +72,25 @@ paths.each do |path|
|
|
|
72
72
|
# needed to tell Opal to wrap the file in an async function and to
|
|
73
73
|
# translate #__await__ calls into real JS await keywords.
|
|
74
74
|
has_magic = source.lines.first(5).any? { |l| l.match?(/#\s*await:/) }
|
|
75
|
+
has_existing_await = source.include?('.__await__')
|
|
75
76
|
|
|
76
77
|
begin
|
|
77
78
|
analyzer = CloudflareWorkers::AutoAwait::Analyzer.new(registry, debug: options[:debug])
|
|
78
79
|
buffer, nodes = analyzer.process(source, path)
|
|
80
|
+
needs_magic_only_output = has_existing_await && !has_magic
|
|
79
81
|
|
|
80
|
-
if nodes.empty?
|
|
82
|
+
if nodes.empty? && !needs_magic_only_output
|
|
81
83
|
puts "[auto-await] skip (no changes) #{rel}" if options[:debug]
|
|
82
84
|
skipped += 1
|
|
83
85
|
next
|
|
84
86
|
end
|
|
85
87
|
|
|
86
|
-
transformed =
|
|
88
|
+
transformed =
|
|
89
|
+
if nodes.empty?
|
|
90
|
+
source
|
|
91
|
+
else
|
|
92
|
+
CloudflareWorkers::AutoAwait::Transformer.transform(source, nodes, buffer)
|
|
93
|
+
end
|
|
87
94
|
unless has_magic
|
|
88
95
|
transformed = "# await: true\n" + transformed
|
|
89
96
|
end
|
data/exe/compile-erb
CHANGED
|
@@ -43,13 +43,40 @@ USAGE
|
|
|
43
43
|
module HomuraERB
|
|
44
44
|
module_function
|
|
45
45
|
|
|
46
|
-
def
|
|
46
|
+
def supported_yield_fragment?(fragment)
|
|
47
47
|
stripped = fragment.strip
|
|
48
|
-
|
|
48
|
+
stripped.match?(/\Ayield(?:\(\s*\))?\z/)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def yield_with_args_fragment?(fragment)
|
|
52
|
+
fragment.strip.match?(/\Ayield\s*\(.+\)\z/)
|
|
53
|
+
end
|
|
49
54
|
|
|
55
|
+
def normalize_fragment(fragment)
|
|
56
|
+
return '__homura_template_yield__' if supported_yield_fragment?(fragment)
|
|
50
57
|
fragment
|
|
51
58
|
end
|
|
52
59
|
|
|
60
|
+
def validate_code_fragment!(fragment)
|
|
61
|
+
stripped = fragment.strip
|
|
62
|
+
return unless supported_yield_fragment?(stripped) || yield_with_args_fragment?(stripped)
|
|
63
|
+
|
|
64
|
+
raise <<~MSG.strip
|
|
65
|
+
Unsupported ERB yield form `<% #{stripped} %>`.
|
|
66
|
+
Use `<%= yield %>` or `<%== yield %>` in layout templates.
|
|
67
|
+
MSG
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def validate_expression_fragment!(fragment)
|
|
71
|
+
stripped = fragment.strip
|
|
72
|
+
return unless yield_with_args_fragment?(stripped)
|
|
73
|
+
|
|
74
|
+
raise <<~MSG.strip
|
|
75
|
+
Unsupported ERB yield form `<%= #{stripped} %>`.
|
|
76
|
+
`yield(arg)` is not supported; use `<%= yield %>` or `<%== yield %>`, or pass data via ivars/locals instead.
|
|
77
|
+
MSG
|
|
78
|
+
end
|
|
79
|
+
|
|
53
80
|
# Compile an ERB source string to a Ruby method body that assembles
|
|
54
81
|
# an HTML string in a local variable `_out`. The body references
|
|
55
82
|
# `@ivars` and method calls directly, so it must be run via
|
|
@@ -85,15 +112,21 @@ module HomuraERB
|
|
|
85
112
|
elsif inner.start_with?('==')
|
|
86
113
|
# `<%== expression %>` — identical to `<%= %>` in this minimal
|
|
87
114
|
# dialect (no HTML escaping yet; the author is responsible).
|
|
88
|
-
expr =
|
|
115
|
+
expr = inner[2..].strip
|
|
116
|
+
validate_expression_fragment!(expr)
|
|
117
|
+
expr = normalize_fragment(expr)
|
|
89
118
|
ruby << "_out = _out + ((#{expr})).to_s\n"
|
|
90
119
|
elsif inner.start_with?('=')
|
|
91
120
|
# `<%= expression %>`
|
|
92
|
-
expr =
|
|
121
|
+
expr = inner[1..].strip
|
|
122
|
+
validate_expression_fragment!(expr)
|
|
123
|
+
expr = normalize_fragment(expr)
|
|
93
124
|
ruby << "_out = _out + ((#{expr})).to_s\n"
|
|
94
125
|
else
|
|
95
126
|
# `<% code %>` — Ruby statement(s), emitted verbatim
|
|
96
|
-
|
|
127
|
+
code = inner.strip
|
|
128
|
+
validate_code_fragment!(code)
|
|
129
|
+
ruby << normalize_fragment(code) << "\n"
|
|
97
130
|
end
|
|
98
131
|
|
|
99
132
|
cursor = close_idx + 2
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
3
4
|
require 'pathname'
|
|
4
5
|
|
|
5
6
|
module CloudflareWorkers
|
|
6
7
|
module BuildSupport
|
|
7
8
|
RUNTIME_GEM_NAME = 'homura-runtime'
|
|
8
9
|
SINATRA_GEM_NAME = 'sinatra-homura'
|
|
10
|
+
SEQUEL_D1_GEM_NAME = 'sequel-d1'
|
|
9
11
|
|
|
10
12
|
class << self
|
|
11
13
|
def loaded_spec(name, loaded_specs: Gem.loaded_specs)
|
|
@@ -37,6 +39,61 @@ module CloudflareWorkers
|
|
|
37
39
|
nil
|
|
38
40
|
end
|
|
39
41
|
|
|
42
|
+
def runtime_file(*names, current_file: __FILE__, loaded_specs: Gem.loaded_specs)
|
|
43
|
+
runtime_root(current_file: current_file, loaded_specs: loaded_specs).join('runtime', *names)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ensure_standalone_runtime(project_root, current_file: __FILE__, loaded_specs: Gem.loaded_specs)
|
|
47
|
+
target_dir = Pathname(project_root).join('cf-runtime')
|
|
48
|
+
FileUtils.mkdir_p(target_dir)
|
|
49
|
+
|
|
50
|
+
%w[setup-node-crypto.mjs worker_module.mjs].each do |name|
|
|
51
|
+
FileUtils.cp(runtime_file(name, current_file: current_file, loaded_specs: loaded_specs), target_dir.join(name))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
target_dir
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def standalone_load_paths(project_root, with_db:, loaded_specs: Gem.loaded_specs)
|
|
58
|
+
root = Pathname(project_root)
|
|
59
|
+
load_paths = []
|
|
60
|
+
|
|
61
|
+
hv = vendor_from_gemfile(root)
|
|
62
|
+
load_paths << hv.to_s if hv
|
|
63
|
+
|
|
64
|
+
load_paths += ['build/auto_await/app', 'app']
|
|
65
|
+
[
|
|
66
|
+
gem_lib(RUNTIME_GEM_NAME, loaded_specs: loaded_specs),
|
|
67
|
+
gem_vendor(RUNTIME_GEM_NAME, loaded_specs: loaded_specs),
|
|
68
|
+
gem_lib(SINATRA_GEM_NAME, loaded_specs: loaded_specs),
|
|
69
|
+
gem_vendor(SINATRA_GEM_NAME, loaded_specs: loaded_specs)
|
|
70
|
+
].compact.each do |path|
|
|
71
|
+
load_paths << path
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if with_db
|
|
75
|
+
[
|
|
76
|
+
gem_vendor(SEQUEL_D1_GEM_NAME, loaded_specs: loaded_specs),
|
|
77
|
+
gem_lib(SEQUEL_D1_GEM_NAME, loaded_specs: loaded_specs)
|
|
78
|
+
].compact.each do |path|
|
|
79
|
+
load_paths << path
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
load_paths << 'vendor' if root.join('vendor').directory?
|
|
84
|
+
load_paths << 'build'
|
|
85
|
+
load_paths.uniq
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def standalone_namespace(project_root, suffix)
|
|
89
|
+
base = Pathname(project_root).basename.to_s
|
|
90
|
+
parts = base.split(/[^A-Za-z0-9]+/).reject(&:empty?)
|
|
91
|
+
module_name = parts.map { |part| part[0].upcase + part[1..].to_s }.join
|
|
92
|
+
module_name = 'App' if module_name.empty?
|
|
93
|
+
module_name = "App#{module_name}" if module_name.match?(/\A\d/)
|
|
94
|
+
"#{module_name}#{suffix}"
|
|
95
|
+
end
|
|
96
|
+
|
|
40
97
|
def vendor_from_gemfile(project_root)
|
|
41
98
|
gf = Pathname(project_root).join('Gemfile')
|
|
42
99
|
return unless gf.file?
|
data/lib/cloudflare_workers.rb
CHANGED
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
# style entry point and never sees a Cloudflare-specific symbol.
|
|
21
21
|
#
|
|
22
22
|
# 3. Cloudflare::D1Database / KVNamespace / R2Bucket — tiny Ruby wrappers
|
|
23
|
-
# around the JS bindings.
|
|
24
|
-
#
|
|
25
|
-
#
|
|
23
|
+
# around the JS bindings. At the raw runtime layer they still return
|
|
24
|
+
# Promises, but homura's build-time auto-await pass rewrites the
|
|
25
|
+
# common Sinatra-facing call sites (`db.execute`, `kv.get`, etc.) so
|
|
26
|
+
# app code usually stays sync-shaped.
|
|
26
27
|
#
|
|
27
28
|
# Note: Opal Strings are immutable (they map to JS Strings), so this file
|
|
28
29
|
# uses reassignment (`@buffer = @buffer + str`) instead of `<<` mutation.
|
|
@@ -209,8 +210,10 @@ module Rack
|
|
|
209
210
|
|
|
210
211
|
# Expose D1 / KV / R2 bindings as plain Ruby wrapper objects.
|
|
211
212
|
# The user Sinatra routes reach them via
|
|
212
|
-
# `env['cloudflare.DB']` / `.KV` / `.BUCKET
|
|
213
|
-
# Ruby methods on them
|
|
213
|
+
# `env['cloudflare.DB']` / `.KV` / `.BUCKET` and call ordinary
|
|
214
|
+
# Ruby methods on them. Under the hood those methods are async,
|
|
215
|
+
# but homura's auto-await build step inserts `.__await__` for the
|
|
216
|
+
# common binding/helper patterns so app source usually does not.
|
|
214
217
|
js_db = `#{js_env} && #{js_env}.DB`
|
|
215
218
|
js_kv = `#{js_env} && #{js_env}.KV`
|
|
216
219
|
js_r2 = `#{js_env} && #{js_env}.BUCKET`
|
|
@@ -603,8 +606,10 @@ module Cloudflare
|
|
|
603
606
|
|
|
604
607
|
# ---- sqlite3-ruby compatible high-level API ----------------------
|
|
605
608
|
|
|
606
|
-
# Execute a SQL statement with optional bind parameters
|
|
607
|
-
#
|
|
609
|
+
# Execute a SQL statement with optional bind parameters.
|
|
610
|
+
# Returns a JS Promise resolving to an Array of Hashes; the build-time
|
|
611
|
+
# auto-await pass rewrites the usual Sinatra call sites so app code can
|
|
612
|
+
# stay `db.execute(...)` instead of spelling `.__await__`.
|
|
608
613
|
#
|
|
609
614
|
# db.execute("SELECT * FROM users") → Array<Hash>
|
|
610
615
|
# db.execute("SELECT * FROM users WHERE id = ?", [1]) → Array<Hash>
|
|
@@ -616,6 +621,7 @@ module Cloudflare
|
|
|
616
621
|
end
|
|
617
622
|
|
|
618
623
|
# Execute and return only the first row (or nil).
|
|
624
|
+
# Returns a JS Promise resolving to a Hash or nil.
|
|
619
625
|
#
|
|
620
626
|
# db.get_first_row("SELECT * FROM users WHERE id = ?", [1]) → Hash or nil
|
|
621
627
|
def get_first_row(sql, bind_params = [])
|
|
@@ -626,6 +632,7 @@ module Cloudflare
|
|
|
626
632
|
|
|
627
633
|
# Execute a write statement (INSERT / UPDATE / DELETE) and return
|
|
628
634
|
# a metadata Hash with `changes`, `last_row_id`, `duration`, etc.
|
|
635
|
+
# Returns a JS Promise resolving to that metadata Hash.
|
|
629
636
|
#
|
|
630
637
|
# meta = db.execute_insert("INSERT INTO users (name) VALUES (?)", ["alice"])
|
|
631
638
|
# meta['last_row_id'] # → 7
|
|
@@ -636,7 +643,7 @@ module Cloudflare
|
|
|
636
643
|
end
|
|
637
644
|
|
|
638
645
|
# Execute one or more raw SQL statements separated by semicolons.
|
|
639
|
-
# Useful for schema migrations. Returns the D1 exec result.
|
|
646
|
+
# Useful for schema migrations. Returns the raw async D1 exec result.
|
|
640
647
|
def execute_batch(sql)
|
|
641
648
|
exec(sql)
|
|
642
649
|
end
|
|
@@ -697,6 +704,7 @@ module Cloudflare
|
|
|
697
704
|
end
|
|
698
705
|
|
|
699
706
|
# KV#get returns a JS Promise resolving to a String or nil.
|
|
707
|
+
# In normal Sinatra app code, auto-await usually hides that Promise.
|
|
700
708
|
def get(key)
|
|
701
709
|
js_kv = @js
|
|
702
710
|
err_cls = Cloudflare::KVError
|
|
@@ -705,7 +713,8 @@ module Cloudflare
|
|
|
705
713
|
|
|
706
714
|
# Put a value. `expiration_ttl:` (seconds) maps to the Workers KV
|
|
707
715
|
# `expirationTtl` option so callers can set TTLs without reaching
|
|
708
|
-
# for backticks. Returns a JS Promise
|
|
716
|
+
# for backticks. Returns a JS Promise; common route/helper call sites
|
|
717
|
+
# are auto-awaited during the build.
|
|
709
718
|
def put(key, value, expiration_ttl: nil)
|
|
710
719
|
js_kv = @js
|
|
711
720
|
err_cls = Cloudflare::KVError
|