wasmify-rails 0.1.0 → 0.1.2

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: 442a1c699d7e906546d63b1d5c6c451652d65ecd651bd16fa72d98841a845acf
4
- data.tar.gz: d8dac2abf684945cc8c9fbd10d3ed6c6236c9535c3f7c3974501638b7be6f607
3
+ metadata.gz: 67904120ba3d147d07ad14d5e15c6f9faf06eb43f3c93d132326407581be4ad7
4
+ data.tar.gz: c88da10a277060fcda66faa34f1334dc246870d4ab0d32655772d0b05ed534a2
5
5
  SHA512:
6
- metadata.gz: 14b9d7c161e577b80d5d9b92d6cb6a7294203f535f938423b194e4adf204541f0d93c4efa466847d2546bf8a919aa96c82037613fb31c7114f3fe1c4debaa5ee
7
- data.tar.gz: 61c8ee0c9f26376f53e22bff246bba40c9d038094d846c17cc7b022f600344c70142777901d28d60eb41a5c8471c4dacbf6c188166a0aa76e1b88554c52900e9
6
+ metadata.gz: 78a1328a1ee3339968a6715b1482012f77dff692b69a55062a7c0eb1c460a7c631c617149a863eac7771c5dcccbd3673fa4fb18ca916005e215615db50bea210
7
+ data.tar.gz: f128859e3c1f7e5ab2eee405afe71e7a5bd9fd593cfe88dae69cf6dd91f70b837c2bfe42364b95da2a1d60507776e28123dc7389a48231f720a5b9acbd599f80
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.1.2
6
+
7
+ - Add cache support to Rack handler.
8
+
9
+ Now we use `caches` for files with `Cache-Control`, so we don't perform a Wasm request.
10
+
11
+ - Minor fixes and improvements.
12
+
13
+ ## 0.1.1
14
+
15
+ - Support multipart file uploads by converting files to data URIs.
16
+
17
+ At the Rack side, we use a `Rack::DataUriUploads` middleware to automatically convert
18
+ data-URI-encoded files to files uploads, so the application can handle them as usual.
19
+
5
20
  ## 0.1.0
6
21
 
7
22
  - Initial release
data/README.md CHANGED
@@ -13,7 +13,7 @@ Adding to your Gemfile:
13
13
  gem "wasmify-rails", group: [:development, :wasm]
14
14
  ```
15
15
 
16
- ## Usage
16
+ ## Usage: generators and tasks
17
17
 
18
18
  This gem comes with a few commands (Rake tasks) to help you get started with packing your Rails app into a Wasm module.
19
19
 
@@ -147,11 +147,35 @@ bin/rails wasmify:pack
147
147
 
148
148
  And go to the `pwa` for the instructions on how to launch the application.
149
149
 
150
+ Here is an example app:
151
+
152
+ <video src="https://github.com/user-attachments/assets/34e54379-5f3e-42eb-a4fa-96c9aaa91869"></video>
153
+
154
+ ## Rails/Ruby extensions
155
+
156
+ This gem provides a variety of _adapters_ and plugins to make your Rails application Wasm-compatible:
157
+
158
+ - Active Record
159
+
160
+ - `sqlite3_wasm` adapter: work with `sqlite3` Wasm just like with a regular SQLite database.
161
+ - `nulldb` adapter for testing purposes.
162
+
163
+ - Active Storage
164
+
165
+ - `null` variant processor (just leaves files as is)
166
+
167
+ - Action Mailer
168
+
169
+ - `null` delivery method (to disable emails in Wasm)
170
+
171
+ - Rack
172
+
173
+ - `Rack::DataUriUploads` middleware to transparently transform Data URI uploads into files.
174
+
150
175
  ## Roadmap
151
176
 
152
177
  - PGLite support (see [this example](https://github.com/kateinoigakukun/mastodon/blob/fff2e4a626a20a616c546ddf4f91766abaf1133a/pwa/dist/pglite.rb#L1))
153
178
  - Active Storage OPFS service
154
- - File uploads support (multipart/form-data)
155
179
  - Background jobs support
156
180
  - WASI Preview 2 support (also [this](https://github.com/kateinoigakukun/mastodon/tree/katei/wasmify))
157
181
 
@@ -161,7 +185,7 @@ Bug reports and pull requests are welcome on GitHub at [https://github.com/](htt
161
185
 
162
186
  ## Credits
163
187
 
164
- The `nulldb` adapter for Active Record (used for tests) is adopted from [this project](https://github.com/nulldb/nulldb).
188
+ The `nulldb` adapter for Active Record (used for tests) is ported from [this project](https://github.com/nulldb/nulldb).
165
189
 
166
190
  ## License
167
191
 
@@ -11,5 +11,5 @@ module ActionMailer
11
11
  end
12
12
 
13
13
  ActiveSupport.on_load(:action_mailer) do
14
- ActionMailer::Base.add_delivery_method :null
14
+ ActionMailer::Base.add_delivery_method :null, ActionMailer::NullDeliveryMethod
15
15
  end
@@ -118,6 +118,7 @@ module ActiveRecord
118
118
  str_val = val.to_s
119
119
  next str_val if val.typeof == "string"
120
120
  next str_val == "true" if val.typeof == "boolean"
121
+ next nil if str_val == "null"
121
122
 
122
123
  # handle integers and floats
123
124
  next str_val.include?(".") ? val.to_f : val.to_i if val.typeof == "number"
@@ -136,14 +137,37 @@ module ActiveRecord
136
137
  end
137
138
  end
138
139
 
140
+ # This type converts byte arrays represented as strings from JS to binaries in Ruby
141
+ class JSBinary < ActiveModel::Type::Binary
142
+ def deserialize(value)
143
+ bvalue = value
144
+ if value.is_a?(String)
145
+ bvalue = value.split(",").map(&:to_i).pack("c*")
146
+ end
147
+
148
+ super(bvalue)
149
+ end
150
+ end
151
+
152
+ ActiveRecord::Type.register(:binary, JSBinary, adapter: :sqlite3_wasm)
153
+
139
154
  class << self
140
155
  def database_exists?(config)
141
156
  true
142
157
  end
143
158
 
144
159
  def new_client(config) = ExternalInterface.new(config)
160
+
161
+ private
162
+ def initialize_type_map(m)
163
+ super
164
+ register_class_with_limit m, %r(binary)i, JSBinary
165
+ end
145
166
  end
146
167
 
168
+ # Re-initialize type map to include JSBinary
169
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
170
+
147
171
  attr_reader :external_interface
148
172
 
149
173
  def initialize(...)
@@ -47,10 +47,15 @@ class Wasmify::InstallGenerator < Rails::Generators::Base
47
47
  end
48
48
  end
49
49
 
50
- def skip_bundler_setup_in_boot
50
+ def configure_boot_file
51
51
  inject_into_file "config/boot.rb", after: /require ['"]bundler\/setup['"]/ do
52
52
  " unless RUBY_PLATFORM =~ /wasm/"
53
53
  end
54
+
55
+ # Disable bootsnap if any
56
+ inject_into_file "config/boot.rb", after: /require ['"]bootsnap\/setup['"]/ do
57
+ " unless RUBY_PLATFORM =~ /wasm/"
58
+ end
54
59
  end
55
60
 
56
61
  def add_tzinfo_data_to_gemfile
@@ -26,5 +26,8 @@ Rails.application.configure do
26
26
  config.active_storage.variant_processor = :null
27
27
  end
28
28
 
29
- config.secret_key_base = "<change-me>"
29
+ # Do not use the same secret key base in a local app (for security reasons)
30
+ config.secret_key_base = "wasm-secret"
31
+ # Use a different session cookie name to avoid conflicts
32
+ config.session_store :cookie_store, key: "_local_session"
30
33
  end
@@ -15,3 +15,4 @@ exclude_gems:
15
15
  - nio4r
16
16
  - io-console
17
17
  - psych
18
+ - date
@@ -4,15 +4,7 @@ export const setupSQLiteDatabase = async () => {
4
4
  const sqlite3 = await sqlite3InitModule();
5
5
 
6
6
  console.log("Running SQLite3 version", sqlite3.version.libVersion);
7
- const db =
8
- "opfs" in sqlite3
9
- ? new sqlite3.oo1.OpfsDb("/railsdb.sqlite3")
10
- : new sqlite3.oo1.DB("/railsdb.sqlite3", "ct");
11
- console.log(
12
- "opfs" in sqlite3
13
- ? `OPFS is available, created persisted database at ${db.filename}`
14
- : `OPFS is not available, created transient database ${db.filename}`,
15
- );
16
-
7
+ // NOTE: This database is transient and will be lost if you uninstall the service worker (aka hard reset)
8
+ const db = new sqlite3.oo1.DB("/railsdb.sqlite3", "ct");
17
9
  return db;
18
10
  };
@@ -12,9 +12,11 @@ let db = null;
12
12
  const initDB = async (progress) => {
13
13
  if (db) return db;
14
14
 
15
- progress.updateStep("Initializing SQLite database...");
15
+ progress?.updateStep("Initializing SQLite database...");
16
16
  db = await setupSQLiteDatabase();
17
- progress.updateStep("SQLite database created.");
17
+ progress?.updateStep("SQLite database created.");
18
+
19
+ return db;
18
20
  };
19
21
 
20
22
  let vm = null;
@@ -47,6 +49,8 @@ const initVM = async (progress, opts = {}) => {
47
49
  vm.eval("ActiveRecord::Tasks::DatabaseTasks.prepare_all");
48
50
 
49
51
  redirectConsole = false;
52
+
53
+ return vm;
50
54
  };
51
55
 
52
56
  const resetVM = () => {
@@ -14,6 +14,7 @@ module ImageProcessing
14
14
  fail ArgumentError, "File not found: #{source}" unless File.file?(source)
15
15
 
16
16
  if destination
17
+ File.delete(destination) if File.identical?(source, destination)
17
18
  FileUtils.cp(source, destination)
18
19
  end
19
20
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+ require "base64"
5
+
6
+ module Rack
7
+ class DataUriUploads
8
+ # A specific prefix we use to identify data URIs that must be transformed
9
+ # into file uploads.
10
+ PREFIX = "BbC14y"
11
+ DATAURI_REGEX = %r{^#{PREFIX}data:(.*?);(.*?),(.*)$}
12
+
13
+ def initialize(app)
14
+ @app = app
15
+ end
16
+
17
+ def call(env)
18
+ return @app.call(env) unless env[RACK_INPUT]
19
+
20
+ request = Rack::Request.new(env)
21
+
22
+ if request.post? || request.put? || request.patch?
23
+ transform_params(request.params)
24
+ end
25
+
26
+ @app.call(env)
27
+ end
28
+
29
+ private
30
+
31
+ def transform_params(params)
32
+ return params unless params.is_a?(Hash)
33
+ params.each do |key, value|
34
+ if value.is_a?(String) && value.match?(DATAURI_REGEX)
35
+ params[key] = from_data_uri(value)
36
+ elsif value.is_a?(Hash)
37
+ transform_params(value)
38
+ elsif value.is_a?(Array)
39
+ value.each { transform_params(_1) }
40
+ end
41
+ end
42
+ end
43
+
44
+ def from_data_uri(data_uri)
45
+ matches = data_uri.match(DATAURI_REGEX)
46
+
47
+ content_type = matches[1]
48
+ encoding = matches[2]
49
+ data = matches[3]
50
+
51
+ file_data = Base64.decode64(data)
52
+
53
+ file = Tempfile.new(["upload", mime_to_extension(content_type)])
54
+ file.binmode
55
+ file.write(file_data)
56
+ file.rewind
57
+
58
+ # Create a Rack::Test::UploadedFile, so it works with strong parameters
59
+ Rack::Test::UploadedFile.new(file.path, content_type)
60
+ end
61
+
62
+ def mime_to_extension(mime_type)
63
+ mime_type.split("/").then do |parts|
64
+ next "" unless parts.length == 2
65
+ ".#{parts.last}"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -6,6 +6,12 @@ module Wasmify
6
6
  rake_tasks do
7
7
  load "wasmify/rails/tasks.rake"
8
8
  end
9
+
10
+ initializer "wasmify.rack_data_uri" do |app|
11
+ require "rack/data_uri_uploads"
12
+
13
+ app.middleware.use Rack::DataUriUploads
14
+ end
9
15
  end
10
16
  end
11
17
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Wasmify
4
4
  module Rails # :nodoc:
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
data/lib/wasmify-rails.rb CHANGED
@@ -18,6 +18,8 @@ module ImageProcessing
18
18
  autoload :Null, "image_processing/null"
19
19
  end
20
20
 
21
+ require "action_mailer/null_delivery"
22
+
21
23
  # NullDB for Active Record
22
24
  ActiveRecord::ConnectionAdapters.register("nulldb", "ActiveRecord::ConnectionAdapters::NullDBAdapter", "active_record/connection_adapters/nulldb_adapter")
23
25
 
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.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-21 00:00:00.000000000 Z
11
+ date: 2024-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -122,6 +122,7 @@ files:
122
122
  - CHANGELOG.md
123
123
  - LICENSE.txt
124
124
  - README.md
125
+ - lib/action_mailer/null_delivery.rb
125
126
  - lib/active_record/connection_adapters/nulldb_adapter.rb
126
127
  - lib/active_record/connection_adapters/nulldb_adapter/checkpoint.rb
127
128
  - lib/active_record/connection_adapters/nulldb_adapter/column.rb
@@ -134,7 +135,6 @@ files:
134
135
  - lib/active_record/connection_adapters/nulldb_adapter/statement.rb
135
136
  - lib/active_record/connection_adapters/nulldb_adapter/table_definition.rb
136
137
  - lib/active_record/connection_adapters/sqlite3_wasm_adapter.rb
137
- - lib/active_storage/null_delivery.rb
138
138
  - lib/generators/wasmify/install/USAGE
139
139
  - lib/generators/wasmify/install/install_generator.rb
140
140
  - lib/generators/wasmify/install/templates/config/environments/wasm.rb
@@ -150,6 +150,7 @@ files:
150
150
  - lib/generators/wasmify/pwa/templates/pwa/rails.sw.js
151
151
  - lib/generators/wasmify/pwa/templates/pwa/vite.config.js
152
152
  - lib/image_processing/null.rb
153
+ - lib/rack/data_uri_uploads.rb
153
154
  - lib/wasmify-rails.rb
154
155
  - lib/wasmify/rails/builder.rb
155
156
  - lib/wasmify/rails/configuration.rb
@@ -178,7 +179,7 @@ metadata:
178
179
  documentation_uri: https://github.com/palkan/wasmify-rails
179
180
  homepage_uri: https://github.com/palkan/wasmify-rails
180
181
  source_code_uri: https://github.com/palkan/wasmify-rails
181
- post_install_message:
182
+ post_install_message:
182
183
  rdoc_options: []
183
184
  require_paths:
184
185
  - lib
@@ -193,8 +194,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
194
  - !ruby/object:Gem::Version
194
195
  version: '0'
195
196
  requirements: []
196
- rubygems_version: 3.5.18
197
- signing_key:
197
+ rubygems_version: 3.5.16
198
+ signing_key:
198
199
  specification_version: 4
199
200
  summary: Tools and extensions to package Rails apps as Wasm modules
200
201
  test_files: []