wasm_rails 0.1.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.
data/lib/wasm_rails.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "wasm_rails/version"
2
+ require "wasm_rails/railtie" if defined?(Rails)
3
+
4
+ module WasmRails
5
+ def self.wasm?
6
+ RUBY_PLATFORM == "wasm32-wasi"
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # WASM stub — io/console/size is a C extension not available in ruby+stdlib.wasm.
2
+ # Returns a fixed terminal size since WASM has no real console.
3
+
4
+ class IO
5
+ def self.console_size = [24, 80]
6
+ def self.default_console_size = [24, 80]
7
+ def winsize = [24, 80]
8
+ end
@@ -0,0 +1,6 @@
1
+ # WASM stub — io/wait C extension not available in ruby+stdlib.wasm.
2
+ class IO
3
+ def wait_readable(timeout = nil) = self
4
+ def wait_writable(timeout = nil) = self
5
+ def ready? = false
6
+ end
@@ -0,0 +1,39 @@
1
+ # WASM stub — Loofah depends on Nokogiri which is not available in WASM.
2
+ # Provides minimal interface for rails-html-sanitizer to load.
3
+
4
+ require "nokogiri"
5
+
6
+ module Loofah
7
+ VERSION = "2.25.1"
8
+
9
+ def self.document(html, *) = Nokogiri::HTML::Document.new(html.to_s)
10
+ def self.fragment(html, *) = Nokogiri::HTML::DocumentFragment.new(html.to_s)
11
+ def self.scrub_document(html, scrubber) = document(html)
12
+ def self.scrub_fragment(html, scrubber) = fragment(html)
13
+
14
+ class Scrubber
15
+ STOP = :stop
16
+ CONTINUE = nil
17
+ attr_accessor :direction
18
+ def initialize(**opts); @direction = opts[:direction] || :bottom_up; end
19
+ def scrub(node) = nil
20
+ end
21
+
22
+ class StripScrubber < Scrubber; end
23
+ class WhitelistScrubber < Scrubber; end
24
+ class SafeListScrubber < Scrubber; end
25
+
26
+ module HTML5
27
+ def self.parse(html, *) = Nokogiri::HTML::Document.new(html.to_s)
28
+ def self.fragment(html, *) = Nokogiri::HTML::DocumentFragment.new(html.to_s)
29
+
30
+ class Document < Nokogiri::HTML::Document; end
31
+ class DocumentFragment < Nokogiri::HTML::DocumentFragment; end
32
+ class SafeDocument < Document; end
33
+ class SafeDocumentFragment < DocumentFragment; end
34
+ end
35
+
36
+ module XssFoliate
37
+ def self.included(base); end
38
+ end
39
+ end
@@ -0,0 +1,67 @@
1
+ # WASM stub — Nokogiri is a C extension not available in ruby+stdlib.wasm.
2
+ # Provides minimal classes so loofah and rails-html-sanitizer can load.
3
+
4
+ module Nokogiri
5
+ VERSION = "1.18.0"
6
+ LIBXML_VERSION = "2.9.14"
7
+
8
+ module HTML
9
+ def self.parse(html, url = nil, encoding = nil, options = 0)
10
+ Document.new(html.to_s)
11
+ end
12
+ def self.fragment(html, encoding = nil, options = 0)
13
+ DocumentFragment.new(html.to_s)
14
+ end
15
+
16
+ class Document
17
+ def initialize(html = ""); @html = html.to_s; end
18
+ def to_s = @html
19
+ def text = @html.gsub(/<[^>]+>/, "")
20
+ def inner_html = @html
21
+ def css(*) = NodeSet.new
22
+ def xpath(*) = NodeSet.new
23
+ def search(*) = NodeSet.new
24
+ def at_css(*) = nil
25
+ def at_xpath(*) = nil
26
+ def encoding = "UTF-8"
27
+ def errors = []
28
+ end
29
+
30
+ class DocumentFragment < Document; end
31
+ end
32
+
33
+ HTML4 = HTML
34
+
35
+ module XML
36
+ Node = HTML::Document
37
+
38
+ class Document < HTML::Document; end
39
+ class DocumentFragment < HTML::DocumentFragment; end
40
+
41
+ class NodeSet
42
+ include Enumerable
43
+ def initialize(items = []) = @items = items
44
+ def each(&b) = @items.each(&b)
45
+ def to_s = @items.map(&:to_s).join
46
+ def text = @items.map { |n| n.respond_to?(:text) ? n.text : n.to_s }.join
47
+ def size = @items.size
48
+ def empty? = @items.empty?
49
+ def [](i) = @items[i]
50
+ def first = @items.first
51
+ def css(*) = NodeSet.new
52
+ def xpath(*) = NodeSet.new
53
+ end
54
+
55
+ module SAX
56
+ class Document; end
57
+ class Parser
58
+ def initialize(doc = nil); end
59
+ def parse(data); end
60
+ end
61
+ end
62
+ end
63
+
64
+ module CSS
65
+ def self.xpath_for(*) = ""
66
+ end
67
+ end
@@ -0,0 +1,122 @@
1
+ # WASM stub — OpenSSL C extension is not included in ruby+stdlib.wasm.
2
+ # Provides the minimal interface ActiveSupport needs for MessageEncryptor,
3
+ # MessageVerifier, and EncryptedConfiguration at load time.
4
+ # Real encryption is not used in WASM (no server secrets, local-only data).
5
+
6
+ module OpenSSL
7
+ VERSION = "3.0.0"
8
+ OPENSSL_VERSION = "OpenSSL 3.0.0 (wasm stub)"
9
+
10
+ # Must be a class (not module) so that SHA256 < OpenSSL::Digest returns true.
11
+ # ActiveSupport::KeyGenerator checks this with klass < OpenSSL::Digest.
12
+ class Digest
13
+ def self.hexdigest(data)
14
+ require "digest"
15
+ ::Digest::SHA256.hexdigest(data.to_s)
16
+ end
17
+ def self.digest(data) = [self.hexdigest(data)].pack("H*")
18
+ def hexdigest(data) = self.class.hexdigest(data)
19
+ def digest(data) = self.class.digest(data)
20
+ def initialize(*); end
21
+ def update(*); self; end
22
+ def reset; self; end
23
+ def finish = ""
24
+
25
+ class SHA1 < Digest
26
+ def self.hexdigest(data)
27
+ require "digest"; ::Digest::SHA1.hexdigest(data.to_s)
28
+ end
29
+ end
30
+
31
+ class SHA256 < Digest
32
+ def self.hexdigest(data)
33
+ require "digest"; ::Digest::SHA256.hexdigest(data.to_s)
34
+ end
35
+ end
36
+
37
+ class SHA384 < Digest
38
+ def self.hexdigest(data)
39
+ require "digest"; ::Digest::SHA384.hexdigest(data.to_s)
40
+ end
41
+ end
42
+
43
+ class SHA512 < Digest
44
+ def self.hexdigest(data)
45
+ require "digest"; ::Digest::SHA512.hexdigest(data.to_s)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Real OpenSSL::Cipher is a class (not a module), so .new works directly.
51
+ class Cipher
52
+ CipherError = Class.new(StandardError)
53
+
54
+ def initialize(algo); @algo = algo.to_s; end
55
+ def authenticated?; @algo.upcase.include?("GCM") || @algo.upcase.include?("CCM"); end
56
+ def encrypt; self; end
57
+ def decrypt; self; end
58
+ def key=(k); end
59
+ def iv=(v); end
60
+ def iv_len = 12
61
+ def key_len = 32
62
+ def padding=(v); end
63
+ def auth_data=(d); end
64
+ def auth_tag(len = 16) = ("\x00" * (len || 16))
65
+ def auth_tag=(t); end
66
+ def update(data) = data
67
+ def final = ""
68
+ def random_key = ("\x00" * 32)
69
+ def random_iv = ("\x00" * 12)
70
+ def block_size = 16
71
+
72
+ class AES < Cipher; end
73
+ end
74
+
75
+ module HMAC
76
+ def self.digest(digest, key, data) = ""
77
+ def self.hexdigest(digest, key, data) = ("00" * 32)
78
+ end
79
+
80
+ module PKCS5
81
+ def self.pbkdf2_hmac(secret, salt, iterations, length, digest)
82
+ require "digest"
83
+ base = ::Digest::SHA256.digest("#{secret}:#{salt}:#{iterations}")
84
+ (base * ((length / 32) + 2))[0, length]
85
+ end
86
+ end
87
+
88
+ module KDF
89
+ def self.hkdf(secret, salt:, info:, length:, hash:) = ("\x00" * length)
90
+ end
91
+
92
+ module Random
93
+ def self.random_bytes(n = 16) = SecureRandom.random_bytes(n)
94
+ end
95
+
96
+ module PKey
97
+ class PKey; end
98
+ class RSA < PKey
99
+ def initialize(*); end
100
+ def public_key = self
101
+ def private? = false
102
+ def sign(*) = ""
103
+ def verify(*) = false
104
+ end
105
+ end
106
+
107
+ module SSL
108
+ VERIFY_NONE = 0
109
+ VERIFY_PEER = 1
110
+ end
111
+
112
+ class BN
113
+ def initialize(n = 0, base = 10); @n = n.to_i; end
114
+ def to_i = @n
115
+ def to_s(base = 10) = @n.to_s(base)
116
+ end
117
+
118
+ class X509
119
+ class Certificate; end
120
+ class Store; end
121
+ end
122
+ end
@@ -0,0 +1,89 @@
1
+ # WASM stub — rails-html-sanitizer depends on loofah+nokogiri.
2
+ # In WASM, all data is user-local, so sanitizers pass through without modification.
3
+ # Defines both Rails::Html (legacy) and Rails::HTML4/HTML5 (rails-html-sanitizer 1.6+).
4
+
5
+ require "loofah"
6
+
7
+ module Rails
8
+ # Base sanitizer shared by all namespaces
9
+ module HtmlSanitizerBase
10
+ class Sanitizer
11
+ def sanitize(html, options = {}) = html.to_s
12
+ def self.full_sanitizer = FullSanitizer.new
13
+ def self.link_sanitizer = LinkSanitizer.new
14
+ def self.safe_list_sanitizer = SafeListSanitizer.new
15
+ def self.white_list_sanitizer = safe_list_sanitizer
16
+ def self.best_supported_vendor = Rails::HTML4
17
+ end
18
+
19
+ class FullSanitizer < Sanitizer
20
+ def sanitize(html, options = {})
21
+ return "" if html.nil?
22
+ html.to_s.gsub(/<[^>]+>/, "")
23
+ end
24
+ end
25
+
26
+ class LinkSanitizer < Sanitizer; end
27
+ class SafeListSanitizer < Sanitizer; end
28
+ WhiteListSanitizer = SafeListSanitizer
29
+
30
+ module Scrubbers
31
+ class Strip < ::Loofah::Scrubber; end
32
+ class SafeList < ::Loofah::Scrubber; end
33
+ WhiteList = SafeList
34
+ end
35
+ end
36
+
37
+ # Legacy namespace (rails-html-sanitizer < 1.6)
38
+ module Html
39
+ include HtmlSanitizerBase
40
+ Sanitizer = HtmlSanitizerBase::Sanitizer
41
+ FullSanitizer = HtmlSanitizerBase::FullSanitizer
42
+ LinkSanitizer = HtmlSanitizerBase::LinkSanitizer
43
+ SafeListSanitizer = HtmlSanitizerBase::SafeListSanitizer
44
+ WhiteListSanitizer = SafeListSanitizer
45
+ Scrubbers = HtmlSanitizerBase::Scrubbers
46
+ end
47
+
48
+ # Rails::HTML is the top-level namespace referenced by railties load_defaults
49
+ module HTML
50
+ Sanitizer = HtmlSanitizerBase::Sanitizer
51
+ FullSanitizer = HtmlSanitizerBase::FullSanitizer
52
+ LinkSanitizer = HtmlSanitizerBase::LinkSanitizer
53
+ SafeListSanitizer = HtmlSanitizerBase::SafeListSanitizer
54
+ WhiteListSanitizer = SafeListSanitizer
55
+ Scrubbers = HtmlSanitizerBase::Scrubbers
56
+
57
+ def self.best_supported_vendor = Rails::HTML4
58
+ end
59
+
60
+ # New namespaces (rails-html-sanitizer 1.6+ / Rails 8)
61
+ # ActionView::SanitizeHelper defaults to Rails::HTML4
62
+ module HTML4
63
+ Sanitizer = HtmlSanitizerBase::Sanitizer
64
+ FullSanitizer = HtmlSanitizerBase::FullSanitizer
65
+ LinkSanitizer = HtmlSanitizerBase::LinkSanitizer
66
+ SafeListSanitizer = HtmlSanitizerBase::SafeListSanitizer
67
+ WhiteListSanitizer = SafeListSanitizer
68
+ Scrubbers = HtmlSanitizerBase::Scrubbers
69
+
70
+ def self.full_sanitizer = FullSanitizer.new
71
+ def self.link_sanitizer = LinkSanitizer.new
72
+ def self.safe_list_sanitizer = SafeListSanitizer.new
73
+ def self.best_supported_vendor = self
74
+ end
75
+
76
+ module HTML5
77
+ Sanitizer = HtmlSanitizerBase::Sanitizer
78
+ FullSanitizer = HtmlSanitizerBase::FullSanitizer
79
+ LinkSanitizer = HtmlSanitizerBase::LinkSanitizer
80
+ SafeListSanitizer = HtmlSanitizerBase::SafeListSanitizer
81
+ WhiteListSanitizer = SafeListSanitizer
82
+ Scrubbers = HtmlSanitizerBase::Scrubbers
83
+
84
+ def self.full_sanitizer = FullSanitizer.new
85
+ def self.link_sanitizer = LinkSanitizer.new
86
+ def self.safe_list_sanitizer = SafeListSanitizer.new
87
+ def self.best_supported_vendor = self
88
+ end
89
+ end
@@ -0,0 +1,19 @@
1
+ # WASM stub — DNS resolution not available in browser WASM.
2
+ module Resolv
3
+ def self.getaddress(name) = "127.0.0.1"
4
+ def self.getaddresses(name) = ["127.0.0.1"]
5
+ def self.getname(addr) = "localhost"
6
+ def self.getnames(addr) = ["localhost"]
7
+
8
+ class DNS; end
9
+ class IPv4
10
+ def self.create(addr) = new(addr)
11
+ def initialize(addr); @addr = addr; end
12
+ def to_s = @addr
13
+ end
14
+ class IPv6
15
+ def self.create(addr) = new(addr)
16
+ def initialize(addr); @addr = addr; end
17
+ def to_s = @addr
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ # WASM stub — socket C extension is not available in ruby+stdlib.wasm.
2
+ # Provides the constants and classes that ipaddr, activesupport, and railties need.
3
+
4
+ class BasicSocket; end
5
+
6
+ class Socket < BasicSocket
7
+ AF_UNSPEC = 0
8
+ AF_UNIX = 1
9
+ AF_INET = 2
10
+ AF_INET6 = 10
11
+ AF_LOCAL = 1
12
+ SOCK_STREAM = 1
13
+ SOCK_DGRAM = 2
14
+ SOCK_RAW = 3
15
+ IPPROTO_IP = 0
16
+ IPPROTO_TCP = 6
17
+ IPPROTO_UDP = 17
18
+ IPPROTO_IPV6 = 41
19
+ IPPORT_RESERVED = 1024
20
+
21
+ def self.gethostname = "localhost"
22
+ def self.getaddrinfo(*) = []
23
+ def self.gethostbyname(*) = nil
24
+ def self.gethostbyaddr(*) = nil
25
+ def self.ip_address_list = []
26
+ def self.pack_sockaddr_in(port, host) = ""
27
+ def self.unpack_sockaddr_in(addr) = [0, "127.0.0.1"]
28
+ end
29
+
30
+ class Addrinfo
31
+ attr_reader :afamily, :pfamily, :socktype, :protocol
32
+ def initialize(*); end
33
+ def self.getaddrinfo(*) = []
34
+ def self.tcp(host, port) = new
35
+ def self.udp(host, port) = new
36
+ def ip? = false
37
+ def ip_address = "127.0.0.1"
38
+ def ip_port = 0
39
+ def ipv4? = false
40
+ def ipv6? = false
41
+ end
42
+
43
+ class TCPSocket < BasicSocket; def initialize(*); end; end
44
+ class TCPServer < BasicSocket; def initialize(*); end; end
45
+ class UDPSocket < BasicSocket; def initialize(*); end; end
46
+ class UNIXSocket < BasicSocket; def initialize(*); end; end
47
+ class UNIXServer < BasicSocket; def initialize(*); end; end
@@ -0,0 +1,102 @@
1
+ # WASM stub for the sqlite3 C extension.
2
+ # Loaded via /stubs (prepended to $LOAD_PATH) before any gem path, so
3
+ # `require 'sqlite3'` resolves here instead of trying to load the native .so.
4
+ # Provides enough constants and classes for sqlite3_adapter.rb to load;
5
+ # the actual DB operations are handled by WasmSqlite3Adapter::ExternalInterface.
6
+
7
+ module SQLite3
8
+ VERSION = '2.5.0'
9
+
10
+ module ForkSafety
11
+ def self.suppress_warnings!; end
12
+ end
13
+
14
+ class Exception < StandardError; end
15
+ class BusyException < Exception; end
16
+ class FullException < Exception; end
17
+ class IOException < Exception; end
18
+ class AccessDeniedException < Exception; end
19
+ class ProtocolException < Exception; end
20
+ class RangeException < Exception; end
21
+ class NotADatabaseException < Exception; end
22
+ class MisuseException < Exception; end
23
+ class CantOpenException < Exception; end
24
+ class NotFoundException < Exception; end
25
+ class CorruptException < Exception; end
26
+ class ConstraintException < Exception; end
27
+
28
+ module Constants
29
+ module TextRep
30
+ UTF8 = 1
31
+ UTF16LE = 2
32
+ UTF16BE = 3
33
+ UTF16 = 4
34
+ ANY = 5
35
+ end
36
+
37
+ module ColumnType
38
+ INTEGER = 1
39
+ FLOAT = 2
40
+ TEXT = 3
41
+ BLOB = 4
42
+ NULL = 5
43
+ end
44
+
45
+ module Open
46
+ READONLY = 0x00000001
47
+ READWRITE = 0x00000002
48
+ CREATE = 0x00000004
49
+ NOMUTEX = 0x00008000
50
+ FULLMUTEX = 0x00010000
51
+ end
52
+ end
53
+
54
+ module Pragmas; end
55
+
56
+ class Statement
57
+ attr_reader :remainder
58
+
59
+ def initialize(db, sql); end
60
+ def close; end
61
+ def step; nil; end
62
+ def columns; []; end
63
+ def types; []; end
64
+ def reset!; end
65
+ def bind_params(*); end
66
+ def execute(*); []; end
67
+ def done?; true; end
68
+ def must_be_open!; end
69
+ def column_count; 0; end
70
+ end
71
+
72
+ class Database
73
+ def self.quote(s) = s.gsub("'", "''")
74
+
75
+ def initialize(file = '', options = {}); end
76
+ def close; end
77
+ def closed?; false; end
78
+ def execute(sql, *binds); []; end
79
+ def execute2(sql, *binds); []; end
80
+ def query(sql, *binds); []; end
81
+ def prepare(sql); Statement.new(self, sql); end
82
+ def transaction(mode = :deferred); yield self if block_given?; end
83
+ def commit; end
84
+ def rollback; end
85
+ def changes; 0; end
86
+ def last_insert_row_id; 0; end
87
+ def errmsg; ''; end
88
+ def errcode; 0; end
89
+ def complete?(sql); true; end
90
+ def busy_handler_timeout=(v); end
91
+ def busy_timeout=(v); end
92
+ def busy_timeout(ms); end
93
+ def extended_result_codes=(v); end
94
+ def results_as_hash=(v); end
95
+ def type_translation=(v); end
96
+ def encoding; 'UTF-8'; end
97
+ def authorizer=(v); end
98
+ def trace(&block); end
99
+
100
+ include Pragmas
101
+ end
102
+ end
@@ -0,0 +1,10 @@
1
+ # WASM is single-threaded — map Thread to Fiber so gems that spawn threads
2
+ # don't raise. The fiber runs synchronously when .value or .join is called.
3
+ class Thread
4
+ def self.new(...)
5
+ f = Fiber.new(...)
6
+ def f.value = resume
7
+ def f.join = value
8
+ f
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wasm_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Emerson Argueta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.1'
27
+ description: Infrastructure gem for building WASM-first Rails apps. Provides a Service
28
+ Worker that boots Ruby+Rails inside the browser, SQLite persistence via OPFS, build
29
+ tooling, and C extension stubs.
30
+ email:
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - MIT-LICENSE
36
+ - README.md
37
+ - lib/generators/wasm_rails/install/install_generator.rb
38
+ - lib/generators/wasm_rails/install/templates/boot.js
39
+ - lib/generators/wasm_rails/install/templates/build_app_bundle.mjs
40
+ - lib/generators/wasm_rails/install/templates/esbuild_wasm.mjs
41
+ - lib/generators/wasm_rails/install/templates/serve_wasm.mjs
42
+ - lib/generators/wasm_rails/install/templates/service_worker.js
43
+ - lib/generators/wasm_rails/install/templates/wasm_shell.html
44
+ - lib/generators/wasm_rails/install/templates/wasm_sqlite3_adapter.rb
45
+ - lib/wasm_rails.rb
46
+ - lib/wasm_rails/railtie.rb
47
+ - lib/wasm_rails/version.rb
48
+ - wasm_stubs/io/console/size.rb
49
+ - wasm_stubs/io/wait.rb
50
+ - wasm_stubs/loofah.rb
51
+ - wasm_stubs/nokogiri.rb
52
+ - wasm_stubs/openssl.rb
53
+ - wasm_stubs/rails-html-sanitizer.rb
54
+ - wasm_stubs/resolv.rb
55
+ - wasm_stubs/socket.rb
56
+ - wasm_stubs/sqlite3.rb
57
+ - wasm_stubs/thread.rb
58
+ homepage: https://github.com/emerson-argueta/wasm-rails
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '3.3'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.5.9
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Run Rails apps in the browser via WebAssembly
81
+ test_files: []