wasmify-rails 0.3.2 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6da25bdd1a3e205a57b7081ada2a29997e4cf5df306423695564aba47591efb3
4
- data.tar.gz: d7e72fc5aed555d506086f267cba686560357c3a478015b891d15e9b613cb26f
3
+ metadata.gz: 4888ee4d3efedd0bd93074f7856cf0be0789bad2ab092c460af84e79603bd0cf
4
+ data.tar.gz: 163b3dbd14d60250ab04f5e6c1d2b2e601b65e79f51b827bcd50e19cf0bb614a
5
5
  SHA512:
6
- metadata.gz: 650516f17e4f8fc0a02f5b6515d23634f8ac47dd14d5faecb0bd932d74994b7a1b9127a50143b46dd97d1d1c6f5b3e7fbba1146306cc9f099dcb44542945c643
7
- data.tar.gz: ac1d4ef0047666f1e7e52e935f3c78dcb596c4c064b7bd7f5ace46ccc8eef56f555f7db4e90d52da50b469f64587a8e4766a47f31e7caed0e3976c35b9573cec
6
+ metadata.gz: '00814ba133827f4b932fe3ee8bce0ff3096fe10ff7b41c28d10ac64353888d9aa0b4078b2c6572b082acf83c693abc7071dfd48eece93fd6d6f8e8e8942bb9b2'
7
+ data.tar.gz: 2ebed2d0efa0f87e3a8685be5799382ef20dc8b93c7817777c3eb8387e88617197a24a36a7931b8e5790fa6771706c4d689c9382ba12e50e1b9adefae08c2191
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.4.0
6
+
7
+ - Backport various patches from Rails tutorial.
8
+
5
9
  ## 0.3.2
6
10
 
7
11
  - Add `Rack::WASI::IncomingHandler` mimicking `wasi/http:proxy` interface.
@@ -107,11 +107,19 @@ module ActiveRecord
107
107
  private attr_reader :js_interface
108
108
 
109
109
  def initialize(config)
110
- @js_interface = config.fetch(:js_interface, "pglite4rails").to_sym
110
+ @js_interface =
111
+ if config[:js_interface]
112
+ config[:js_interface]
113
+ else
114
+ # Set up the database in JS and get the idenfier back
115
+ JS.global[:pglite].create_interface(config[:database]).await.to_s
116
+ end.to_sym
111
117
  @last_result = nil
112
118
  @prepared_statements_map = {}
113
119
  end
114
120
 
121
+ def finished? = true
122
+
115
123
  def set_client_encoding(encoding)
116
124
  end
117
125
 
@@ -52,7 +52,9 @@ module ActiveRecord
52
52
 
53
53
  def busy_timeout(...) = nil
54
54
 
55
- def busy_handler_timeout=(...) = nil
55
+ def busy_handler_timeout=(...)
56
+ nil
57
+ end
56
58
 
57
59
  def execute(sql)
58
60
  @last_statement = Statement.new(self, sql)
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ module Tasks # :nodoc:
3
+ class PGliteDatabaseTasks # :nodoc:
4
+ def self.using_database_configurations?
5
+ true
6
+ end
7
+
8
+ attr_reader :db_config, :configuration_hash
9
+
10
+ def initialize(db_config)
11
+ @db_config = db_config
12
+ @configuration_hash = db_config.configuration_hash
13
+ end
14
+
15
+ def create(connection_already_established = false)
16
+ JS.global[:pglite].create_interface(db_config.database).await
17
+ end
18
+
19
+ def purge(...)
20
+ # skip for now
21
+ end
22
+ end
23
+ end
24
+ end
@@ -144,6 +144,7 @@ module Rack
144
144
  headers["HTTP_#{key.upcase.gsub("-", "_")}"] = value
145
145
  headers
146
146
  end
147
+
147
148
  http_method = req.method.upcase
148
149
  headers[:method] = http_method
149
150
 
@@ -167,7 +168,7 @@ module Rack
167
168
 
168
169
  # Serve images as base64 from Ruby and decode back in JS
169
170
  # FIXME: extract into a separate middleware and add a header to indicate the transformation
170
- if headers["Content-Type"]&.start_with?("image/")
171
+ if response_headers["Content-Type"]&.start_with?("image/")
171
172
  body = Base64.strict_encode64(body)
172
173
  end
173
174
 
@@ -0,0 +1,16 @@
1
+ module Rackup
2
+ module Handler
3
+ class WASIServer
4
+ def self.run(app, **options)
5
+ require "rack/wasi/incoming_handler"
6
+ port = options[:Port]
7
+
8
+ $incoming_handler = ::Rack::WASI::IncomingHandler.new(app)
9
+
10
+ ::Wasmify::ExternalCommands.server(port)
11
+ end
12
+ end
13
+
14
+ register :wasi, WASIServer
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ module Wasmify
2
+ module ExternalCommands
3
+ class << self
4
+ attr_reader :command
5
+
6
+ def register(*names)
7
+ names.each do |name|
8
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
9
+ def self.#{name}(...)
10
+ raise ArgumentError, "Command has been already defined: #{command}" if command
11
+
12
+ ::JS.global[:externalCommands].#{name}(...)
13
+
14
+ @command = :#{name}
15
+ end
16
+ RUBY
17
+ end
18
+ end
19
+
20
+ def any? = !!command
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # Use TracePoint to wait for a particular class to load,
2
+ # so we can apply a patch right away
3
+ module Wasmify
4
+ module Patcha
5
+ class << self
6
+ def on_load(name, &callback)
7
+ callbacks[name] = callback
8
+ end
9
+
10
+ def on_class(event)
11
+ # Ignore singletons
12
+ return if event.self.singleton_class?
13
+
14
+ class_name = name_method.bind_call(event.self)
15
+
16
+ return unless callbacks[class_name]
17
+
18
+ clbk = callbacks.delete(class_name)
19
+ tracer.disable if callbacks.empty?
20
+
21
+ clbk.call
22
+ end
23
+
24
+ def setup!
25
+ return if callbacks.empty?
26
+
27
+ @tracer = TracePoint.new(:end, &method(:on_class))
28
+ tracer.enable
29
+ # Use `Module#name` instead of `self.name` to handle overwritten `name` method
30
+ @name_method = Module.instance_method(:name)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :tracer, :name_method
36
+
37
+ def callbacks = @callbacks ||= {}
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ Wasmify::Patcha.on_load("ActionText::PlainTextConversion") do
4
+ module ActionText::PlainTextConversion
5
+ def plain_text_for_node(node)
6
+ html = node.to_html
7
+ # FIXME: use external interface?
8
+ html.gsub(/<[^>]*>/, " ").strip
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ Wasmify::Patcha.on_load("Rails::Server") do
2
+ Rails::Server.prepend(Module.new do
3
+ def initialize(options)
4
+ # disable pid files
5
+ options.delete(:pid)
6
+ super
7
+ end
8
+
9
+ # Change the after_stop_callback logic
10
+ def start(after_stop_callback = nil)
11
+ Kernel.at_exit(&after_stop_callback) if after_stop_callback
12
+ super()
13
+ end
14
+ end)
15
+ end
16
+
17
+ Wasmify::Patcha.on_load("Rails::Generators::Actions") do
18
+ Rails::Generators::Actions.prepend(Module.new do
19
+ # Always run Rails commands inline (we cannot spawn new processes)
20
+ def rails_command(command, options = {})
21
+ super(command, options.merge(inline: true))
22
+ end
23
+ end)
24
+ end
25
+
26
+ Wasmify::Patcha.on_load("Rails::Console::IRBConsole") do
27
+ Rails::Console::IRBConsole.prepend(Module.new do
28
+ def start
29
+ # Disable default IRB behaviour but keep the configuration around
30
+ ::IRB::Irb.prepend(Module.new { def run(*); end })
31
+ super
32
+ ::Wasmify::ExternalCommands.console
33
+ end
34
+ end)
35
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "wasmify/patcha"
4
+
5
+ require "wasmify/rails/patches/rails"
6
+ require "wasmify/rails/patches/action_text"
7
+
8
+ Wasmify::Patcha.setup!
@@ -16,7 +16,6 @@ module Wasmify
16
16
  end
17
17
  end
18
18
 
19
-
20
19
  require "action_mailer/null_delivery"
21
20
 
22
21
  # NullDB for Active Record
@@ -27,3 +26,6 @@ ActiveRecord::ConnectionAdapters.register("sqlite3_wasm", "ActiveRecord::Connect
27
26
 
28
27
  # PGlite adapter
29
28
  ActiveRecord::ConnectionAdapters.register("pglite", "ActiveRecord::ConnectionAdapters::PGliteAdapter", "active_record/connection_adapters/pglite_adapter")
29
+
30
+ require "active_record/tasks/pglite_database_tasks"
31
+ ActiveRecord::Tasks::DatabaseTasks.register_task(/pglite/, "ActiveRecord::Tasks::PGliteDatabaseTasks")
@@ -29,9 +29,30 @@ end
29
29
 
30
30
  # Misc patches
31
31
 
32
+ # Stub drb/unix for test parallelization
33
+ $LOADED_FEATURES << $LOAD_PATH.resolve_feature_path("drb/unix")[1]
34
+
32
35
  # Make gem no-op
33
36
  define_singleton_method(:gem) { |*| nil }
34
37
 
38
+ module FileUtils
39
+ # Touch doesn't work for some reason
40
+ def self.touch(path) = File.write(path, "")
41
+ end
42
+
43
+ # Support Kernel.at_exit
44
+ module Kernel
45
+ @@at_exit_hooks = []
46
+
47
+ def at_exit(&block)
48
+ @@at_exit_hooks << block
49
+ end
50
+
51
+ def execute_at_exit_hooks
52
+ @@at_exit_hooks.reverse_each { _1.call }
53
+ end
54
+ end
55
+
35
56
  # Patch Bundler.require to simply require files without looking at specs
36
57
  def Bundler.require(*groups)
37
58
  Bundler.definition.dependencies_for([:wasm]).each do |dep|
@@ -68,6 +89,7 @@ class Thread
68
89
  def self.new(...)
69
90
  f = Fiber.new(...)
70
91
  def f.value = resume
92
+ def f.join = value
71
93
  f
72
94
  end
73
95
  end
@@ -0,0 +1,165 @@
1
+ # This is a minimal Nokogiri stub to make Action Text basic features work (no embeddings)
2
+ module Nokogiri
3
+ module XML
4
+ class DocumentFragment
5
+ end
6
+
7
+ module Node
8
+ module SaveOptions
9
+ AS_HTML = 1
10
+ end
11
+ end
12
+ end
13
+
14
+ module HTML5
15
+ class Document
16
+ def initialize
17
+ # TODO
18
+ end
19
+
20
+ def encoding=(enc)
21
+ end
22
+
23
+ def fragment(html)
24
+ DocumentFragment.new(html)
25
+ end
26
+
27
+ def create_element(tag_name, attributes = {})
28
+ # Create an element with given tag name and attributes
29
+ end
30
+ end
31
+
32
+ class DocumentFragment < XML::DocumentFragment
33
+ attr_reader :elements, :name
34
+
35
+ def initialize(html_string)
36
+ @html_string = html_string
37
+ @elements = []
38
+ @name = html_string.match(/<(\w+)/).then { next unless _1; _1[1] }
39
+ end
40
+
41
+ def css(selector)
42
+ # Return array of matching nodes
43
+ # Must support selectors like:
44
+ # - "a[href]" - anchor tags with href attribute
45
+ # - "action-text-attachment" - elements by tag name
46
+ # - Complex selectors for attachment galleries
47
+ []
48
+ end
49
+
50
+ def children
51
+ []
52
+ end
53
+
54
+ def dup
55
+ self.class.new(@html_string)
56
+ end
57
+
58
+ def to_html(options = {})
59
+ # Must respect options[:save_with] if provided
60
+ @html_string
61
+ end
62
+
63
+ def elements
64
+ self
65
+ end
66
+
67
+ def deconstruct
68
+ children
69
+ end
70
+ end
71
+
72
+ class Node
73
+ attr_accessor :parent
74
+
75
+ def initialize(name, attributes = {})
76
+ @name = name
77
+ @attributes = attributes
78
+ @children = []
79
+ @text = ""
80
+ end
81
+
82
+ # Node type and content
83
+ def name
84
+ @name # e.g., "p", "div", "text", etc.
85
+ end
86
+
87
+ def text
88
+ # Return text content (for text nodes) or aggregate text of children
89
+ end
90
+
91
+ def text?
92
+ # Return true if this is a text node
93
+ name == "text" || name == "#text"
94
+ end
95
+
96
+ # Attribute access
97
+ def [](attribute_name)
98
+ # Get attribute value
99
+ @attributes[attribute_name]
100
+ end
101
+
102
+ def []=(attribute_name, value)
103
+ # Set attribute value
104
+ @attributes[attribute_name] = value
105
+ end
106
+
107
+ def key?(attribute_name)
108
+ # Check if attribute exists
109
+ @attributes.key?(attribute_name)
110
+ end
111
+
112
+ def remove_attribute(attribute_name)
113
+ # Remove attribute and return its value
114
+ @attributes.delete(attribute_name)
115
+ end
116
+
117
+ # DOM traversal
118
+ def children
119
+ # Return array of child nodes
120
+ @children
121
+ end
122
+
123
+ def ancestors
124
+ # Return array of ancestor nodes (parent, grandparent, etc.)
125
+ result = []
126
+ node = parent
127
+ while node
128
+ result << node
129
+ node = node.parent
130
+ end
131
+ result
132
+ end
133
+
134
+ # DOM manipulation
135
+ def replace(replacement)
136
+ # Replace this node with the replacement (string or node)
137
+ # If replacement is a string, parse it as HTML
138
+ end
139
+
140
+ # CSS matching
141
+ def matches?(selector)
142
+ # Check if this node matches the given CSS selector
143
+ end
144
+
145
+ # HTML output
146
+ def to_html(options = {})
147
+ # Convert to HTML string
148
+ end
149
+
150
+ def to_s
151
+ to_html
152
+ end
153
+
154
+ def inspect
155
+ # For debugging
156
+ "#<Node:#{name} #{@attributes.inspect}>"
157
+ end
158
+ end
159
+ end
160
+
161
+ module HTML4
162
+ Document = HTML5::Document
163
+ DocumentFragment = HTML5::DocumentFragment
164
+ end
165
+ end
@@ -138,6 +138,9 @@ module Rails
138
138
  def full_sanitizer = self
139
139
  def link_sanitizer = self
140
140
  def safe_list_sanitizer = self
141
+ def allowed_tags = []
142
+ def allowed_attributes = []
143
+ def allowed_styles = []
141
144
  end
142
145
 
143
146
  def sanitize(html, ...) = html
@@ -9,6 +9,10 @@ end
9
9
  class Socket < BasicSocket
10
10
  AF_UNSPEC = 0
11
11
  AF_INET = 1
12
+
13
+ def self.gethostname
14
+ "localhost"
15
+ end
12
16
  end
13
17
 
14
18
  class IPSocket < Socket
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Wasmify
4
4
  module Rails # :nodoc:
5
- VERSION = "0.3.2"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wasmify-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-09 00:00:00.000000000 Z
11
+ date: 2025-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -137,6 +137,7 @@ files:
137
137
  - lib/active_record/connection_adapters/pglite_adapter.rb
138
138
  - lib/active_record/connection_adapters/pglite_shims/pg.rb
139
139
  - lib/active_record/connection_adapters/sqlite3_wasm_adapter.rb
140
+ - lib/active_record/tasks/pglite_database_tasks.rb
140
141
  - lib/generators/wasmify/install/USAGE
141
142
  - lib/generators/wasmify/install/install_generator.rb
142
143
  - lib/generators/wasmify/install/templates/config/environments/wasm.rb
@@ -154,10 +155,16 @@ files:
154
155
  - lib/image_processing/null.rb
155
156
  - lib/rack/data_uri_uploads.rb
156
157
  - lib/rack/wasi/incoming_handler.rb
158
+ - lib/rackup/handler/wasi.rb
157
159
  - lib/wasmify-rails.rb
160
+ - lib/wasmify/external_commands.rb
161
+ - lib/wasmify/patcha.rb
158
162
  - lib/wasmify/rails/builder.rb
159
163
  - lib/wasmify/rails/configuration.rb
160
164
  - lib/wasmify/rails/packer.rb
165
+ - lib/wasmify/rails/patches.rb
166
+ - lib/wasmify/rails/patches/action_text.rb
167
+ - lib/wasmify/rails/patches/rails.rb
161
168
  - lib/wasmify/rails/railtie.rb
162
169
  - lib/wasmify/rails/shim.rb
163
170
  - lib/wasmify/rails/shims/io/console.rb