homura-runtime 0.2.10 → 0.2.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64648aa358cfa6cd1c65fff6da71068b244c136017c896aa002308eb391a571b
4
- data.tar.gz: 7c02ed80302c02f183e7927c9bd15c25dc1389a2e3921ebbc7bd60cc2622a574
3
+ metadata.gz: 30dae4fa7db65c3e931dedfd413a8a64c76477522ce0f1c51dc7cdd7b878d856
4
+ data.tar.gz: e3d93d0bbe8416b247df3da2cac86e3bbe2c09337bea37c4f6c03aea2df045f0
5
5
  SHA512:
6
- metadata.gz: 2d41fbab3ad2faef7a283e824f66918fbbeb71ce38b3c82597109df9d69cdb5b7f7e89ddb6beca6aa1965f71e26c1791d44019b46217eebfdb6760b9daddac1e
7
- data.tar.gz: ad1208ab207ed735d5effacb6f26a4df562652cf0f53e96acee461e7d6c4fdc397cb5d46016e515077bf0c84065918e6daae29e378a000a0b279b062fe061e1e
6
+ metadata.gz: e112c045281a25b0adce47868e8cda5c6a890d80cdcfe38f20babc3cf00c4172fb2e8197990287c431b1e29d5d820521a94afa100b73c3f42e5763f32e2367be
7
+ data.tar.gz: de25414d62f1463c10cb7e2c214f6ca86f825be14a16705122d187f17f6efc9e6e3e241264162db50d677052ec4309affb2d81c6d6f8f0110a1b623d63e2dee3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.11 (2026-04-25)
4
+
5
+ - Normalize bare JS `undefined` / `null` values to Ruby `nil` while converting
6
+ D1 rows into Ruby Hashes, preventing first-row crashes when a nullable column
7
+ is present in the result shape.
8
+
3
9
  ## 0.2.10 (2026-04-24)
4
10
 
5
11
  - Derive `worker.entrypoint.mjs` import paths relative to the actual
data/exe/compile-erb CHANGED
@@ -136,10 +136,13 @@ module HomuraERB
136
136
  end
137
137
 
138
138
  # Wrap the compiled body in a `#{namespace}.register` call.
139
+ # `template_name` may be a Symbol whose name contains a `/`
140
+ # (e.g. `:"posts/index"`); use `inspect` so the literal serialises
141
+ # as `:"posts/index"` and is parseable by Opal.
139
142
  def wrap(template_name, compiled_body, namespace)
140
143
  indented = compiled_body.each_line.map { |l| " #{l}" }.join
141
144
  <<~RUBY
142
- #{namespace}.register(:#{template_name}) do |locals = {}|
145
+ #{namespace}.register(#{template_name.inspect}) do |locals = {}|
143
146
  #{indented.chomp}
144
147
  end
145
148
  RUBY
@@ -151,11 +154,19 @@ end
151
154
  # ---------------------------------------------------------------------------
152
155
 
153
156
  def default_inputs_for(dir)
154
- Dir.glob(File.join(dir, '*.erb')).sort
157
+ # Recursive so `views/posts/index.erb` becomes `:'posts/index'`,
158
+ # matching upstream Sinatra's subdirectory convention.
159
+ Dir.glob(File.join(dir, '**', '*.erb')).sort
155
160
  end
156
161
 
157
- def template_name_for(path)
158
- File.basename(path, '.erb').to_sym
162
+ def template_name_for(path, root)
163
+ rel = path.dup
164
+ if root
165
+ root_with_sep = root.end_with?('/') ? root : "#{root}/"
166
+ rel = rel[root_with_sep.length..] if rel.start_with?(root_with_sep)
167
+ end
168
+ rel = File.basename(rel) unless rel.include?('/')
169
+ rel.delete_suffix('.erb').to_sym
159
170
  end
160
171
 
161
172
  def emit_header(io, namespace)
@@ -209,13 +220,13 @@ def emit_header(io, namespace)
209
220
  RUBY
210
221
  end
211
222
 
212
- def emit_templates(io, inputs, namespace)
223
+ def emit_templates(io, inputs, namespace, root)
213
224
  inputs.each do |path|
214
- name = template_name_for(path)
225
+ name = template_name_for(path, root)
215
226
  source = File.read(path)
216
227
  compiled = HomuraERB.compile(source)
217
228
  io.puts
218
- io.puts "# ---- #{path} -> :#{name} --------------------------------------"
229
+ io.puts "# ---- #{path} -> :#{name.inspect} --------------------------------------"
219
230
  io.puts HomuraERB.wrap(name, compiled, namespace)
220
231
  end
221
232
  end
@@ -256,9 +267,14 @@ def emit_sinatra_patch(io, namespace)
256
267
  # have one. Unknown symbols raise a clear message instead of
257
268
  # wandering into upstream Tilt, which would blow up on
258
269
  # Workers with "Code generation from strings disallowed".
270
+ #
271
+ # Sinatra's idiom `erb :foo, locals: { x: 1 }` passes locals via
272
+ # `options[:locals]` rather than the third positional argument;
273
+ # merge them so both forms work.
259
274
  def erb(template, options = {}, locals = {}, &block)
275
+ template = template.to_sym if template.is_a?(::String)
260
276
  if template.is_a?(::Symbol) && ::#{namespace}.registered?(template)
261
- locals ||= {}
277
+ locals = (options[:locals] || {}).merge(locals || {})
262
278
  block ||= __homura_default_template_block_for__(template)
263
279
  output = ::#{namespace}.render(template, self, locals, &block)
264
280
 
@@ -315,11 +331,13 @@ namespace = options[:namespace]
315
331
 
316
332
  inputs =
317
333
  if positional.any?
318
- positional.flat_map { |a| File.directory?(a) ? Dir.glob(File.join(a, '*.erb')).sort : [a] }
334
+ positional.flat_map { |a| File.directory?(a) ? Dir.glob(File.join(a, '**', '*.erb')).sort : [a] }
319
335
  else
320
336
  default_inputs_for(options[:input_dir])
321
337
  end
322
338
 
339
+ template_root = positional.find { |a| File.directory?(a) } || options[:input_dir]
340
+
323
341
  if inputs.empty?
324
342
  warn "bin/compile-erb: no .erb files found (under #{options[:input_dir]}/ or from args)"
325
343
  exit 1
@@ -332,12 +350,12 @@ if write_file
332
350
  FileUtils.mkdir_p(File.dirname(out_path))
333
351
  File.open(out_path, 'w') do |io|
334
352
  emit_header(io, namespace)
335
- emit_templates(io, inputs, namespace)
353
+ emit_templates(io, inputs, namespace, template_root)
336
354
  emit_sinatra_patch(io, namespace)
337
355
  end
338
356
  warn "compile-erb: wrote #{out_path} (#{inputs.size} templates)"
339
357
  else
340
358
  emit_header($stdout, namespace)
341
- emit_templates($stdout, inputs, namespace)
359
+ emit_templates($stdout, inputs, namespace, template_root)
342
360
  emit_sinatra_patch($stdout, namespace)
343
361
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloudflareWorkers
4
- VERSION = '0.2.10'
4
+ VERSION = '0.2.12'
5
5
  end
@@ -504,8 +504,13 @@ module Cloudflare
504
504
  while i < len
505
505
  k = `#{keys}[#{i}]`
506
506
  v = `#{js_obj}[#{k}]`
507
+ # Normalize bare JS null/undefined to Ruby nil before storing them.
508
+ if `#{v} == null`
509
+ v = nil
507
510
  # Recurse for nested plain objects (but not Arrays, Dates, etc.)
508
- v = js_object_to_hash(v) if `typeof #{v} === 'object' && #{v} != null && !Array.isArray(#{v}) && !(#{v} instanceof Date)`
511
+ elsif `typeof #{v} === 'object' && !Array.isArray(#{v}) && !(#{v} instanceof Date)`
512
+ v = js_object_to_hash(v)
513
+ end
509
514
  h[k] = v
510
515
  i += 1
511
516
  end
@@ -661,12 +666,34 @@ module Cloudflare
661
666
  # a metadata Hash with `changes`, `last_row_id`, `duration`, etc.
662
667
  # Returns a JS Promise resolving to that metadata Hash.
663
668
  #
669
+ # The raw D1 `run()` payload nests these fields under `meta`:
670
+ # { 'success' => true, 'meta' => { 'last_row_id' => 7, ... }, 'results' => [...] }
671
+ # sqlite3-ruby exposes them at the top level. To keep the documented
672
+ # "sqlite3-ruby compatible" surface and still leave the raw shape
673
+ # available, flatten the common keys to the top level.
674
+ #
664
675
  # meta = db.execute_insert("INSERT INTO users (name) VALUES (?)", ["alice"])
665
- # meta['last_row_id'] # → 7
676
+ # meta['last_row_id'] # → 7 (flattened convenience)
677
+ # meta['meta']['last_row_id'] # → 7 (raw D1 shape)
666
678
  def execute_insert(sql, bind_params = [])
667
679
  stmt = prepare(sql)
668
680
  stmt = stmt.bind(*bind_params) unless bind_params.empty?
669
- stmt.run
681
+ raw = stmt.run
682
+ D1Database.flatten_meta(raw)
683
+ end
684
+
685
+ # Flatten common keys from `result['meta']` to the top of the hash so
686
+ # callers can write `meta['last_row_id']` without descending into the
687
+ # nested D1 metadata object. Preserves the original shape unchanged.
688
+ def self.flatten_meta(result)
689
+ return result unless result.is_a?(Hash)
690
+ nested = result['meta']
691
+ return result unless nested.is_a?(Hash)
692
+
693
+ %w[last_row_id changes duration size_after rows_read rows_written].each do |k|
694
+ result[k] = nested[k] unless result.key?(k)
695
+ end
696
+ result
670
697
  end
671
698
 
672
699
  # Execute one or more raw SQL statements separated by semicolons.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: homura-runtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.10
4
+ version: 0.2.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuhiro Homma
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 1.8.3.rc1.3
18
+ version: 1.8.3.rc1.4
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 1.8.3.rc1.3
25
+ version: 1.8.3.rc1.4
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: parser
28
28
  requirement: !ruby/object:Gem::Requirement