wasmify-rails 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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: []