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 +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/active_record/connection_adapters/pglite_adapter.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3_wasm_adapter.rb +3 -1
- data/lib/active_record/tasks/pglite_database_tasks.rb +24 -0
- data/lib/rack/wasi/incoming_handler.rb +2 -1
- data/lib/rackup/handler/wasi.rb +16 -0
- data/lib/wasmify/external_commands.rb +23 -0
- data/lib/wasmify/patcha.rb +40 -0
- data/lib/wasmify/rails/patches/action_text.rb +11 -0
- data/lib/wasmify/rails/patches/rails.rb +35 -0
- data/lib/wasmify/rails/patches.rb +8 -0
- data/lib/wasmify/rails/railtie.rb +3 -1
- data/lib/wasmify/rails/shim.rb +22 -0
- data/lib/wasmify/rails/shims/nokogiri.rb +165 -0
- data/lib/wasmify/rails/shims/rails-html-sanitizer.rb +3 -0
- data/lib/wasmify/rails/shims/socket.rb +4 -0
- data/lib/wasmify/rails/version.rb +1 -1
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4888ee4d3efedd0bd93074f7856cf0be0789bad2ab092c460af84e79603bd0cf
|
4
|
+
data.tar.gz: 163b3dbd14d60250ab04f5e6c1d2b2e601b65e79f51b827bcd50e19cf0bb614a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '00814ba133827f4b932fe3ee8bce0ff3096fe10ff7b41c28d10ac64353888d9aa0b4078b2c6572b082acf83c693abc7071dfd48eece93fd6d6f8e8e8942bb9b2'
|
7
|
+
data.tar.gz: 2ebed2d0efa0f87e3a8685be5799382ef20dc8b93c7817777c3eb8387e88617197a24a36a7931b8e5790fa6771706c4d689c9382ba12e50e1b9adefae08c2191
|
data/CHANGELOG.md
CHANGED
@@ -107,11 +107,19 @@ module ActiveRecord
|
|
107
107
|
private attr_reader :js_interface
|
108
108
|
|
109
109
|
def initialize(config)
|
110
|
-
@js_interface =
|
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
|
|
@@ -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
|
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
|
@@ -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")
|
data/lib/wasmify/rails/shim.rb
CHANGED
@@ -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
|
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.
|
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-
|
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
|