wasmify-rails 0.3.1 → 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 +8 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/core.rb +1 -1
- data/lib/active_record/connection_adapters/pglite_adapter.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3_wasm_adapter.rb +4 -0
- data/lib/active_record/tasks/pglite_database_tasks.rb +24 -0
- data/lib/rack/data_uri_uploads.rb +3 -1
- data/lib/rack/wasi/incoming_handler.rb +183 -0
- 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 +30 -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 +10 -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
@@ -325,7 +325,7 @@ class ActiveRecord::ConnectionAdapters::NullDBAdapter < ActiveRecord::Connection
|
|
325
325
|
|
326
326
|
def new_table_definition(adapter = nil, table_name = nil, is_temporary = nil, options = {})
|
327
327
|
case ::ActiveRecord::VERSION::MAJOR
|
328
|
-
when 6, 7
|
328
|
+
when 6, 7, 8
|
329
329
|
TableDefinition.new(self, table_name, temporary: is_temporary, options: options.except(:id))
|
330
330
|
when 5
|
331
331
|
TableDefinition.new(table_name, is_temporary, options.except(:id), nil)
|
@@ -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
|
@@ -19,7 +19,9 @@ module Rack
|
|
19
19
|
|
20
20
|
request = Rack::Request.new(env)
|
21
21
|
|
22
|
-
if
|
22
|
+
if (
|
23
|
+
request.post? || request.put? || request.patch?
|
24
|
+
) && request.get_header("HTTP_CONTENT_TYPE").match?(%r{multipart/form-data})
|
23
25
|
transform_params(request.params)
|
24
26
|
env["action_dispatch.request.request_parameters"] = request.params
|
25
27
|
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
require "uri"
|
5
|
+
require "base64"
|
6
|
+
require "rack"
|
7
|
+
require "rack/test"
|
8
|
+
|
9
|
+
require "rack/data_uri_uploads"
|
10
|
+
|
11
|
+
module Rack
|
12
|
+
module WASI
|
13
|
+
class Result < Data.define(:value, :error)
|
14
|
+
def tag = error ? "error" : "ok"
|
15
|
+
end
|
16
|
+
|
17
|
+
# resource incoming-request {
|
18
|
+
# method: func() -> method;
|
19
|
+
# path-with-query: func() -> option<string>;
|
20
|
+
# scheme: func() -> option<scheme>;
|
21
|
+
# authority: func() -> option<string>;
|
22
|
+
# headers: func() -> headers;
|
23
|
+
# consume: func() -> result<incoming-body>;
|
24
|
+
# }
|
25
|
+
# https://github.com/WebAssembly/wasi-http/blob/d163277b8684483a2334363ca1492ca298ea526d/wit/types.wit#L274
|
26
|
+
class IncomingRequest
|
27
|
+
# We use a reference to the global JS object to access the incoming request data.
|
28
|
+
def initialize(js_object_id)
|
29
|
+
@js_object = ::JS.global[js_object_id]
|
30
|
+
end
|
31
|
+
|
32
|
+
def method = @js_object.call(:method).to_s
|
33
|
+
|
34
|
+
def path_with_query
|
35
|
+
path = @js_object.call(:pathWithQuery)
|
36
|
+
if path.typeof == "string"
|
37
|
+
path.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def scheme
|
42
|
+
sch = @js_object.call(:scheme)
|
43
|
+
if sch.typeof == "string"
|
44
|
+
sch.to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def authority
|
49
|
+
auth = @js_object.call(:authority)
|
50
|
+
if auth.typeof == "string"
|
51
|
+
auth.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def headers
|
56
|
+
entries = ::JS.global[:Object].entries(@js_object.call(:headers))
|
57
|
+
entries.to_a.each.with_object({}) do |entry_val, acc|
|
58
|
+
key, val = entry_val.to_a
|
59
|
+
acc[key.to_s] = val.to_s
|
60
|
+
acc
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# NOTE: Currently, we only support text bodies
|
65
|
+
def consume
|
66
|
+
body = @js_object.call(:consume)
|
67
|
+
if body.typeof == "string"
|
68
|
+
body.to_s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# resource response-outparam {
|
74
|
+
# set: static func(
|
75
|
+
# param: response-outparam,
|
76
|
+
# response: result<outgoing-response, error-code>,
|
77
|
+
# );
|
78
|
+
# }
|
79
|
+
#
|
80
|
+
# https://github.com/WebAssembly/wasi-http/blob/d163277b8684483a2334363ca1492ca298ea526d/wit/types.wit#L437
|
81
|
+
class ResponseOutparam
|
82
|
+
# We use a reference to the global JS object to access the incoming request data
|
83
|
+
def initialize(js_object_id)
|
84
|
+
@js_object = ::JS.global[js_object_id]
|
85
|
+
end
|
86
|
+
|
87
|
+
def set(result)
|
88
|
+
@js_object.call(:set, ::JS::Object.wrap(result))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# resource outgoing-response {
|
93
|
+
# constructor(headers: headers);
|
94
|
+
# status-code: func() -> status-code;
|
95
|
+
# set-status-code: func(status-code: status-code) -> result;
|
96
|
+
# headers: func() -> headers;
|
97
|
+
# body: func() -> result<outgoing-body>;
|
98
|
+
# }
|
99
|
+
#
|
100
|
+
# resource outgoing-body {
|
101
|
+
# write: func() -> result<output-stream>;
|
102
|
+
# finish: static func(
|
103
|
+
# this: outgoing-body,
|
104
|
+
# trailers: option<trailers>
|
105
|
+
# ) -> result<_, error-code>;
|
106
|
+
# }
|
107
|
+
# }
|
108
|
+
#
|
109
|
+
# https://github.com/WebAssembly/wasi-http/blob/d163277b8684483a2334363ca1492ca298ea526d/wit/types.wit#L572
|
110
|
+
class OutgoingResponse
|
111
|
+
attr_reader :status_code, :headers, :body
|
112
|
+
|
113
|
+
def initialize(headers:, status_code: 200)
|
114
|
+
@headers = headers
|
115
|
+
@status_code = status_code
|
116
|
+
@body = nil
|
117
|
+
end
|
118
|
+
|
119
|
+
def write(response_body)
|
120
|
+
@body = response_body
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# wasi:http/proxy-like handler implementation for Rack apps
|
125
|
+
class IncomingHandler
|
126
|
+
private attr_reader :base_url
|
127
|
+
|
128
|
+
def initialize(app, base_url: "http://localhost:3000", skip_data_uri_uploads: false)
|
129
|
+
@app = app
|
130
|
+
|
131
|
+
@app = Rack::DataUriUploads.new(@app) unless skip_data_uri_uploads
|
132
|
+
|
133
|
+
@base_url = base_url
|
134
|
+
end
|
135
|
+
|
136
|
+
# Takes Wasi request, converts it to Rack request,
|
137
|
+
# calls the Rack app, and write Rack response back to the Wasi response.
|
138
|
+
#
|
139
|
+
# @param [Rack::WASI::HTTP::IncomingRequest] req
|
140
|
+
# @param [Rack::WASI::HTTP::ResponseOutparam] res
|
141
|
+
def handle(req, res)
|
142
|
+
uri = URI.join(base_url, req.path_with_query || "")
|
143
|
+
headers = req.headers.each.with_object({}) do |(key, value), headers|
|
144
|
+
headers["HTTP_#{key.upcase.gsub("-", "_")}"] = value
|
145
|
+
headers
|
146
|
+
end
|
147
|
+
|
148
|
+
http_method = req.method.upcase
|
149
|
+
headers[:method] = http_method
|
150
|
+
|
151
|
+
body = req.consume
|
152
|
+
headers[:input] = StringIO.new(body) if body
|
153
|
+
|
154
|
+
request = Rack::MockRequest.env_for(uri.to_s, headers)
|
155
|
+
begin
|
156
|
+
response = Rack::Response[*@app.call(request)]
|
157
|
+
response_status, response_headers, bodyiter = *response.finish
|
158
|
+
|
159
|
+
out_response = OutgoingResponse.new(headers: response_headers, status_code: response_status)
|
160
|
+
|
161
|
+
body = ""
|
162
|
+
body_is_set = false
|
163
|
+
|
164
|
+
bodyiter.each do |part|
|
165
|
+
body += part
|
166
|
+
body_is_set = true
|
167
|
+
end
|
168
|
+
|
169
|
+
# Serve images as base64 from Ruby and decode back in JS
|
170
|
+
# FIXME: extract into a separate middleware and add a header to indicate the transformation
|
171
|
+
if response_headers["Content-Type"]&.start_with?("image/")
|
172
|
+
body = Base64.strict_encode64(body)
|
173
|
+
end
|
174
|
+
|
175
|
+
out_response.write(body) if body_is_set
|
176
|
+
res.set(Result.new(out_response, nil))
|
177
|
+
rescue Exception => e
|
178
|
+
res.set(Result.new(e.message, 503))
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -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
@@ -19,11 +19,40 @@ require "bundler"
|
|
19
19
|
# Load core classes and deps patches
|
20
20
|
$LOAD_PATH.unshift File.expand_path("shims", __dir__)
|
21
21
|
|
22
|
+
# Prevent features:
|
23
|
+
# - `bundler/setup` — we do that manually via `/bundle/setup`#
|
24
|
+
%w[
|
25
|
+
bundler/setup
|
26
|
+
].each do |feature|
|
27
|
+
$LOAD_PATH.resolve_feature_path(feature)&.then { $LOADED_FEATURES << _1[1] }
|
28
|
+
end
|
29
|
+
|
22
30
|
# Misc patches
|
23
31
|
|
32
|
+
# Stub drb/unix for test parallelization
|
33
|
+
$LOADED_FEATURES << $LOAD_PATH.resolve_feature_path("drb/unix")[1]
|
34
|
+
|
24
35
|
# Make gem no-op
|
25
36
|
define_singleton_method(:gem) { |*| nil }
|
26
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
|
+
|
27
56
|
# Patch Bundler.require to simply require files without looking at specs
|
28
57
|
def Bundler.require(*groups)
|
29
58
|
Bundler.definition.dependencies_for([:wasm]).each do |dep|
|
@@ -60,6 +89,7 @@ class Thread
|
|
60
89
|
def self.new(...)
|
61
90
|
f = Fiber.new(...)
|
62
91
|
def f.value = resume
|
92
|
+
def f.join = value
|
63
93
|
f
|
64
94
|
end
|
65
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
|
@@ -153,10 +154,17 @@ files:
|
|
153
154
|
- lib/generators/wasmify/pwa/templates/pwa/vite.config.js
|
154
155
|
- lib/image_processing/null.rb
|
155
156
|
- lib/rack/data_uri_uploads.rb
|
157
|
+
- lib/rack/wasi/incoming_handler.rb
|
158
|
+
- lib/rackup/handler/wasi.rb
|
156
159
|
- lib/wasmify-rails.rb
|
160
|
+
- lib/wasmify/external_commands.rb
|
161
|
+
- lib/wasmify/patcha.rb
|
157
162
|
- lib/wasmify/rails/builder.rb
|
158
163
|
- lib/wasmify/rails/configuration.rb
|
159
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
|
160
168
|
- lib/wasmify/rails/railtie.rb
|
161
169
|
- lib/wasmify/rails/shim.rb
|
162
170
|
- lib/wasmify/rails/shims/io/console.rb
|